插头dp小结

都1202年了,怎么还有人不会轮廓线dp啊,明明8002年就有论文了…

参考资料:

c d q cdq cdq, a _ f o r e v e r _ k i n g a\_forever\_king a_forever_king

轮廓线 d p dp dp本质上是一种基于连通性状态压缩的动态规划.

例题1

https://www.luogu.com.cn/problem/P5056

给出 n × m n\times m n×m 的方格,有些格子不能铺线,其它格子必须铺,形成一个闭合回路。问有多少种铺法?

n , m ≤ 12 n,m\le 12 n,m12.

数据范围这么小,明示状压.

一种转移方法是按行/列转移,这种方法需要枚举整行的状态后处理,冗余状态较多.

另一种方法就是轮廓线 d p dp dp 用到的按格转移的方法啦.

先给出一些定义:

  • 插头: 一个格子连出的有向边. 对于一个4连通图,有上下左右四个方向的插头.

  • 轮廓线: 我们定义已决策格子和未决策格子直接的分割线.
    在这里插入图片描述
    图中的蓝线就是轮廓线啦~

    可以发现:

    我们在对 ( i , j ) (i,j) (i,j) 考虑的时候的时候,轮廓线中的两个地方会发生变化.

    轮廓线有 m + 1 m+1 m+1 个段.

对于一条哈密顿回路,每个点都被遍历一次,也就是只有两个插头被用到.

插头的连通性是我们关心的问题,所以我们不妨定义状态 f i , j , S f_{i,j,S} fi,j,S 表示 加入了 ( i , j ) (i,j) (i,j) 后, m + 1 m+1 m+1 个插头的连通状态为 S S S 的方案数.

S S S 有可以使用两种最小表示法压缩:

  • 从小到大枚举轮廓线上的段, 如果没有插头记为0, 如果有插头且没有标记那么 d f s dfs dfs 把所有与其联通的插头标记为一个更大的标号.

    如上,第二个图可以表示为 { 1 , 0 , 1 , 2 , 2 } \{1,0,1,2,2\} {1,0,1,2,2}.

  • 无插头记为0, 否则用连通块中列数最小值作为标记.同样的例子得到 { 1 , 0 , 1 , 3 , 3 } \{ 1,0,1,3,3\} {1,0,1,3,3}.

其中第一种更加直观. 但是直接状压, 一般要取基数为 m / 2 + 1 m/2+1 m/2+1,导致状压值比较大.

现在介绍一种更简单的方法: 括号表示法:

对于轮廓线连续的点 a , b , c , d a,b,c,d a,b,c,d. 显然若 a , c a,c a,c联通, a , b a,b a,b不连通,那么 b , d b,d b,d 也不连通.(反证法,若 b , d b,d b,d 联通,那么 b d , a c bd,ac bd,ac必定相交, 那么与哈密顿回路的定义矛盾)

同时,任意一个连通块都仅有两个插头与轮廓线相交。

两两匹配和互不交叉容易让人想到“括号匹配”。

这样轮廓线上就有3类情况:

  1. 无插头
  2. 左插头
  3. 右插头

我们可以用3进制。但是4进制运算更方便。

由于有用的状态很少,所以我们可以每次清空哈希表,然后把转移的状态全部塞进去去重,这样常数会小很多。

每次转移的时候 ( i , j ) (i,j) (i,j) 左上插头为轮廓线上的情况,右下为新轮廓线的情况。

我们只要转移的时候讨论一下即可。

x , y x,y x,y 分别为左插头,上插头的表示。(0,1,2分别为无,左括号,右括号)

讲一下一些比较特殊的情况:

x = 1 , y = 1 x=1,y=1 x=1,y=1直接连接。然后把和 y y y 配对的括号(2)变成左括号(1)
x = 2 , y = 2 x=2,y=2 x=2,y=2直接连接.然后把和 x x x配对的括号(1)变成右括号(2)
x = 1 , y = 2 x=1,y=2 x=1,y=2由于这种情况是形成回路且没有用后面的格子.所以当且仅当是最后一个格子时后效.
x = 2 , y = 1 x=2,y=1 x=2,y=1直接连接.
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define REP(i, a, b) for(int i = (b); i >= (a); i--)
#define FOR(i, n) rep(i, 1, n)
#define get(x, y) ((x) >> Bit[y] & 3)
#define ll long long 
using namespace std;
const int N = 14, S = 14010, mod = 13333;

int n, m, a[N][N], bin[N], Bit[N], ex, ey, len[2], cur, last[mod];
struct Node{int x, nxt; ll v;} f[2][S];

void add(int x, ll val) {
	int v = x % mod;
	for(int k = last[v]; k; k = f[cur][k].nxt)
		if(f[cur][k].x == x) {f[cur][k].v += val; return; }
	f[cur][++len[cur]] = (Node){x, last[v], val}; last[v] = len[cur];
}

int main() {
	scanf("%d %d ", &n, &m);
	char s[N];
	FOR(i, n) {
		scanf("%s", s + 1);
		FOR(j, m) {
			a[i][j] = (s[j] == '.');
			if(a[i][j]) ex = i, ey = j;
		}
	}
	bin[0] = 1; FOR(i, m) bin[i] = bin[i - 1] * 4, Bit[i] = 2 * i;
	ll ans = 0; f[cur][len[cur] = 1] = (Node){0, 0, 1};
	FOR(i, ex) {
		FOR(j, len[cur]) f[cur][j].x <<= 2;
		FOR(j, m) {
			memset(last, 0, sizeof last); len[cur ^= 1] = 0;
			FOR(k, len[!cur]) {
				int now = f[!cur][k].x, x = get(now, j - 1), y = get(now, j);
				const ll F = f[!cur][k].v;
				if(!a[i][j]) { if(!x && !y) add(now, F);}
				else if(!x && !y) { if(a[i][j + 1] && a[i + 1][j]) add(now + bin[j - 1] + 2 * bin[j], F);}
				else if(!x) {
					if(a[i][j + 1]) add(now, F);
					if(a[i + 1][j]) add(now + y * (bin[j - 1] - bin[j]), F);
				}
				else if(!y) {
					if(a[i][j + 1]) add(now + x * (bin[j] - bin[j - 1]), F);
					if(a[i + 1][j]) add(now, F);
				}
				else if(x == 1 && y == 1) {
					int c = 1; rep(u, j + 1, m) {
						switch(get(now, u)) {
							case 1: c++; break;
							case 2: c--; break;
						} if(!c) { add(now - bin[j - 1] - bin[j] - bin[u], F); break;}
					}
				} 
				else if(x == 2 && y == 2) {
					int c = -1; REP(u, 0, j - 2) {
						switch(get(now, u)) {
							case 1: c++; break;
							case 2: c--; break;
						} if(!c) { add(now - 2 * (bin[j - 1] + bin[j]) + bin[u], F); break;}
					}
				} 
				else if(x == 2 && y == 1) add(now - x * bin[j - 1] - y * bin[j], F);
				else if(i == ex && j == ey) ans += F; //x = 1, y = 2 时后面的格子都没有用上.
			}
			if(i == ex && j == ey) break;
		}
	} printf("%lld\n", ans); return 0;
}

例题2

https://www.luogu.com.cn/problem/P5074

这不是和上面的题差不多吗

只要允许 x = 1 , y = 2 x=1,y=2 x=1,y=2 在任意格子即可.

#include<bits/stdc++.h>
#define gc getchar()
#define TP template<class o>
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define REP(i, a, b) for(int i = b; i >= a; i--)
#define FOR(i, n) rep(i, 1, n)
using namespace std;
typedef unsigned ui;
typedef long long ll;
typedef unsigned long long ull;
const int N = 14, S = 14010, mod = 40001;

TP void qr(o &x) {
	char c = gc; x = 0; int f = 1;
	while(!isdigit(c)) {if(c == '-') f = -1; c = gc;}
	while(isdigit(c)) x = x * 10 + c - '0', c = gc;
	x *= f;
}

TP void qw(o x) {
	if(x < 0) putchar('-'), x = -x;
	if(x / 10) qw(x/10);
	putchar(x % 10 + '0');
}
TP void pr1(o x) {qw(x); putchar(' ');}
TP void pr2(o x) {qw(x); puts("");}

int n, m, a[N][N], bin[N], len[2], cur, last[mod], vis[mod], num;
struct Node {int x, nxt; ll v; } f[2][S];

void add(int x, ll val) {
	int v = x % mod;
	if(vis[v] ^ num) last[v] = 0, vis[v] = num;
	for(int k = last[v]; k; k = f[cur][k].nxt) 
		if(f[cur][k].x == x) {f[cur][k].v += val; return; }
	f[cur][++len[cur]] = (Node){x, last[v], val}; last[v] = len[cur];
}

#define get(x, y) ((x) >> ((y) * 2) & 3)
void clear() { num++; len[cur] = 0;}

void solve() {
	qr(n); qr(m); memset(a, 0, sizeof a);
	FOR(i, n) FOR(j, m) qr(a[i][j]);
	clear(); add(0, 1);
	FOR(i, n) {
		FOR(s, len[cur]) f[cur][s].x <<= 2;
		FOR(j, m) {
			cur ^= 1; clear();
			FOR(s, len[!cur]) {
				int now = f[!cur][s].x, x = get(now, j - 1), y = get(now, j);
				ll F = f[!cur][s].v;
				if(!a[i][j]) { if(!x && !y) add(now, F); }
				else if(!x && !y) { if(a[i][j + 1] && a[i + 1][j]) add(now + bin[j - 1] + 2 * bin[j], F); }
				else if(!x) {
					if(a[i + 1][j]) add(now + y * (bin[j - 1] - bin[j]), F);
					if(a[i][j + 1]) add(now, F);
				} 
				else if(!y) {
					if(a[i][j + 1]) add(now + x * (bin[j] - bin[j - 1]), F);
					if(a[i + 1][j]) add(now, F);
				}
				else if(x + y == 3) add(now - x * bin[j - 1] - y * bin[j], F);
				else if(x == 1 && y == 1) {
					int c = 1; rep(k, j + 1, m) {
						switch(get(now, k)) {
							case 1: c++; break;
							case 2: c--; break;
						} if(!c) { add(now - bin[j - 1] - bin[j] - bin[k], F); break;}
					}
				}
				else if(x == 2 && y == 2) {
					int c = -1; REP(k, 0, j - 2) {
						switch(get(now, k)) {
							case 1: c++; break;
							case 2: c--; break;
						} if(!c) { add(now - 2 * (bin[j - 1] + bin[j]) + bin[k], F); break;}
					}
				}
			}
		}
	}
	ll ans = 0;
	FOR(k, len[cur]) ans += f[cur][k].v;
	pr2(ans);
}

int main() {
	rep(i, 0, N - 1) bin[i] = 1 << (2 * i);
	int T; qr(T); while(T--) solve();
	return 0;
}

但是,注意到我们上一题之所以要区分左右括号,是因为只能有一个回路.

但是这题,我们显然不需要区分它们了啦,所以直接用2进制压缩状态即可.

位运算的博大精深

#include<cstdio>
#define FOR(i, n) for(int i = 0; i < n; i++)
using namespace std;

int n, m, s, a[12][12], T; 
long long f[1<<13], g[1<<13];
void qr(int &x) {scanf("%d",&x);}

int main() {
	qr(T); while(T--) {
		qr(n); qr(m); s = 1 << m + 1;
		FOR(i, n) FOR(j, m) qr(a[i][j]);
		FOR(i, s) g[i] = 0; g[0] = 1;
		FOR(i, n) FOR(j, m) {
			FOR(k, s) f[k] = j ? g[k] : (k & 1 ? 0 : g[k >> 1]);
			if(!a[i][j]) FOR(k, s) g[k] = k >> j & 3 ? 0 : f[k];
			else {int v = 3 << j; FOR(k, s) g[k] = f[k ^ v] + ((k >> j & 1) ^ (k >> j + 1 & 1) ? f[k] : 0);}
		} printf("%lld\n", g[0]);
	}
}
 

例题3

https://uoj.ac/problem/141

首先,对于一个有经过 i i i次的点,必有 ⌊ n 2 ⌋ \lfloor\dfrac n 2\rfloor 2n 次经过右边和下边.

所以我们可以递推出每个点至少被经过多少次.

剩下至多 n m nm nm个球.

我们考虑轮廓线dp求落在篮子的数量.

状态各维度为:位置,各个插头有多少个球落下,有多少个球入篮子了.

#include<bits/stdc++.h>
#define gc getchar()
#define TP template<class o>
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define REP(i, a, b) for(int i = b; i >= a; i--)
#define FOR(i, n) rep(i, 1, n)
using namespace std;
typedef unsigned ui;
typedef long long ll;
typedef unsigned long long ull;
const int N = 12, B = 51, M = 2333337, mod = 998244353;

TP void qr(o &x) {
	char c = gc; x = 0; int f = 1;
	while(!isdigit(c)) {if(c == '-') f = -1; c = gc;}
	while(isdigit(c)) x = x * 10 + c - '0', c = gc;
	x *= f;
}

TP void qw(o x) {
	if(x < 0) putchar('-'), x = -x;
	if(x / 10) qw(x/10);
	putchar(x % 10 + '0');
}
TP void pr1(o x) {qw(x); putchar(' ');}
TP void pr2(o x) {qw(x); puts("");}

int n, m;
ll k, w[N][N], sum, S[N * N], p[N];
char a[N], b[N];

int len[2], cur, last[M], vis[M], num;
struct Node {int nxt, v; ll x; int y; } f[2][1 << 21];
//x表示状压值. y表示已经入篮子的球数. 

void clear() {num++; len[cur] = 0;}

TP void add(o &x, int y) {x += y; if(x >= mod) x -= mod;}

void ADD(ll x, int y, int val) {
	int v = (x * 23 + 23987) % M;
	if(vis[v] ^ num) vis[v] = num, last[v] = 0;
	for(int k = last[v]; k; k = f[cur][k].nxt)
		if(f[cur][k].x == x && f[cur][k].y == y) return add(f[cur][k].v, val);
	f[cur][++len[cur]] = (Node){last[v], val, x, y}; last[v] = len[cur];
}

ll pw[N];
inline int get(ll x, int y) {
	return x / pw[y] % B;
}

int main() {
	qr(n); qr(m); qr(k);
	scanf("%s %s", a + 1, b + 1);
	w[1][1] = k; FOR(i, n) FOR(j, m) w[i][j + 1] += w[i][j] / 2, w[i + 1][j] += w[i][j] / 2, w[i][j] &= 1;
	sum = 0; pw[0] = 1; FOR(i, m) pw[i] = pw[i - 1] * B;
	FOR(i, n) {a[i] -= '0'; if(a[i] == 1) sum += w[i][m + 1];}
	FOR(i, m) {b[i] -= '0'; if(b[i] == 1) sum += w[n + 1][i]; }
	f[cur][len[cur] = 1] = (Node){0, 1, 0, 0};
	FOR(i, n) {
		FOR(s, len[cur]) f[cur][s].x *= B;
		FOR(j, m) {
			cur ^= 1; clear();
			FOR(s, len[!cur]) {
				ull now_x = f[!cur][s].x; int now_y = f[!cur][s].y, F = f[!cur][s].v, x = get(now_x, j - 1), y = get(now_x, j);
				int u = (x + y + w[i][j]) / 2, v = (x + y + w[i][j]) & 1;
				now_y += ((i == n) * b[j] + (j == m) * a[i]) * u; 
				now_x -= x * pw[j - 1] + y * pw[j];
				now_x += (((j != m) * u) * B + ((i !=	 n) * u)) * pw[j - 1];
				if(v) {
					if(i == n) ADD(now_x, now_y + b[j], F);
					else ADD(now_x + pw[j - 1], now_y, F);
					if(j == m) ADD(now_x, now_y + a[i], F);
					else ADD(now_x + pw[j], now_y, F);
				}
				else ADD(now_x, now_y, F * 2 % mod);
			}
		}
	}
	FOR(s, len[cur]) add(S[f[cur][s].y], f[cur][s].v);
	FOR(i, n * m) S[i] += S[i - 1];
	int q; ll l, r; qr(q); while(q--) {
		qr(l); qr(r);
		l = max(l - sum, 0ll);
		r = min(r - sum, (ll) n * m);
		if(l > r) puts("0");
		else pr2((S[r] - (l ? S[l - 1] : 0)) % mod);
	}
	return 0;
}


例题4

https://www.luogu.com.cn/problem/P3170

自己看着办吧

为了减少状态数,我们要保证插头数+已完成 ′ L ′ ≤ 3 'L'\le 3 L3.

#include<bits/stdc++.h>
#define gc getchar()
#define TP template<class o>
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define REP(i, a, b) for(int i = b; i >= a; i--)
#define FOR(i, n) rep(i, 1, n)
using namespace std;
typedef unsigned ui;
typedef long long ll;
typedef unsigned long long ull;
const int N = 33, S = N * N * N * 3, mod = 233333;

TP void qr(o &x) {
	char c = gc; x = 0; int f = 1;
	while(!isdigit(c)) {if(c == '-') f = -1; c = gc;}
	while(isdigit(c)) x = x * 10 + c - '0', c = gc;
	x *= f;
}

TP void qw(o x) {
	if(x < 0) putchar('-'), x = -x;
	if(x / 10) qw(x/10);
	putchar(x % 10 + '0');
}
TP void pr1(o x) {qw(x); putchar(' ');}
TP void pr2(o x) {qw(x); puts("");}

int n, m, a[N][N], bin[N], len[2], cur, last[mod], vis[mod], num;
struct Node {int x, c, nxt; ll v;} f[2][S];
inline void clear() {num++; len[cur ^= 1] = 0;}
inline void add(int x, int c, ll val) {
	int v = x % mod;
	if(vis[v] ^ num) vis[v] = num, last[v] = 0;
	for(int k = last[v]; k; k = f[cur][k].nxt)	
		if(f[cur][k].x == x && f[cur][k].c == c) { f[cur][k].v += val; return ; }
	f[cur][++len[cur]] = (Node){x, c, last[v], val}; last[v] = len[cur];
}

char s[N];
#define get(x, y) ((x) >> (y) & 1)

int cnt[1 << 15];
const int V = (1 << 15) - 1;
int popcount(int x) {return cnt[x & V] + cnt[x >> 15 & V];}

int main() {
	FOR(i, V) cnt[i] = cnt[i & (i - 1)] + 1;
	qr(n); qr(m);
	FOR(i, n) {
		scanf("%s", s + 1);
		FOR(j, m) a[i][j] = (s[j] == '.');
	}
	bin[0] = 1; FOR(i, m) bin[i] = bin[i - 1] * 2;
	ll ans = 0; f[cur][++len[cur]] = (Node){0, 0, 0, 1};
	FOR(i, n) {
		FOR(s, len[cur]) f[cur][s].x <<= 1;
		FOR(j, m) {
			clear(); 
			FOR(s, len[!cur]) {
				register int now = f[!cur][s].x, c = f[!cur][s].c, x = get(now, j - 1), y = get(now, j), one = popcount(now) + c;
				const ll F = f[!cur][s].v;
				if(!a[i][j]) {if(!x && !y) add(now, c, F); }
				else if(x && y) continue;
				else if(x) {
					if(a[i][j + 1]) add(now + bin[j] - bin[j - 1], c, F);
					if(c < 3) {
						now -= bin[j - 1];
						if(c < 2) add(now, c + 1, F); //end
						else ans += F * (!now);
					}
				}
				else {
					if(y) {
						if(a[i][j + 1]) add(now, c, F);
						if(a[i + 1][j]) add(now - bin[j] + bin[j - 1], c, F);
					}
					else {
						if(a[i + 1][j] && one < 3) add(now + bin[j - 1], c, F);
						add(now, c, F);
					}
				}
			}
		}
	}
	pr2(ans);
}
/*
30 30 
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................

*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinite_Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值