都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,m≤12.
数据范围这么小,明示状压.
一种转移方法是按行/列转移,这种方法需要枚举整行的状态后处理,冗余状态较多.
另一种方法就是轮廓线 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类情况:
- 无插头
- 左插头
- 右插头
我们可以用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 ′L′≤3.
#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
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
..............................
*/