基于连通性的状压DP问题。

一般是给你一个网格,有一些连通性的限制。

例题

插头DP模板

链接

题意:网格图,去掉一些点,求哈密顿回路方案数。

一般按格递推(从上到下,从左到右)。

每个格子要从四个方向中选两个作出边。

我们只需要记录红色的轮廓线的状态,是否有边伸出这个线(称之为插头), 还要记录伸出来的边的连通性。

记录连通性的方法:

  1. 最小表示法
  2. 括号表示法(适用范围较小,效率一般更高):出边是两两配对的(如果没有回来就有终点了);并且出边不可能交叉(因为如果有交叉就经过重复点了)。咱们用三进制表示,\((\) 对应 \(1\),\()\) 对应 \(2\),没有边对应 \(0\)。看似最坏状态是 \(3 ^ {n + 1}\) 的,但要保证括号配对,所以大概有效状态会很少,因此不要以最坏复杂度来分析插头 DP,大概可以打个表式一下极限数据。

设 \(f_{i, j, s}\) 为考虑到 \(i, j\) 当前轮廓线状态是 \(S\) 的方案数。

分类讨论,设上状态为 \(y\),左状态为 \(x\):

  1. 如果 \((i, j)\) 是障碍物。需要 \(x = y = 0\)。状态不变。
  2. 否则,若 \(x = y = 0\),则 \(x \gets 1, y \gets 1\)
  3. \(x = 0\),\(y \not= 0\),枚举一下 \(y\) 从下面和右边出去两种情况。
  4. \(x \not= 0\),\(y = 0\),同 3,向下或向右走。
  5. \(x = y = 1\),必然要连起来,把右边配对的两个插头较左的 \(2\) 变成 \(1\)。(即 \(y\) 的配对变成 \(1\))
  6. \(x = y = 2\),同 5 ,把 \(x\) 对应的配对插头变成 \(2\)。
  7. \(x = 2, y = 1\),把两个插头去掉赋 \(0\)。
  8. \(x = 1, y = 2\),将整个回路封死,只能在整个格子的最后一个格子去封。(只会发生在最后一个格子)。

想要把代码变得美一点、短一点,大概是做到了吧...

这里没有用哈希表,把 \(42000\) 个状态先 dfs 出来,存到数组里,再预处理一下每个状态每对括号的匹配。

这样每次转移可以 \(O(1)\),但是由于状态对应到编号我用的二分,所以复杂度是 \(O(n^2 S \log S)\),其中 \(S\) 是总状态数大概是 \(S \le 42000\)。

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <unordered_map>
  5. using namespace std;
  6. typedef long long LL;
  7. const int N = 15, S = 42000;
  8. int n, m, d[S], tot, w, c[N], p[S][N], now[N], top, s[N], L;
  9. int ex, ey;
  10. LL ans, f[2][S], h[S];
  11. bool st[N][N];
  12. char g[N][N];
  13. int inline query(int x) { return lower_bound(d + 1, d + 1 + tot, x) - d; }
  14. int inline ask(int x, int i) { return x >> (2 * i) & 3; }
  15. int inline get(int i, int t) { return t << (2 * i); }
  16. void inline add(int a, int b) { f[w][query(b)] += f[!w][a]; }
  17. void inline work(int x) {
  18. memset(now, -1, sizeof now); top = 0;
  19. for (int i = 0; i < 2 * L; i += 2) {
  20. int t = x >> i & 3;
  21. if (t == 1) s[++top] = i >> 1;
  22. else if (t == 2) now[s[top]] = i >> 1, now[i >> 1] = s[top--];
  23. }
  24. d[++tot] = x;
  25. for (int i = 0; i < L; i++) p[tot][i] = now[i];
  26. }
  27. void dfs(int u, int s, int cnt) {
  28. if (u == -1) { if (!cnt) work(s); return ; }
  29. dfs(u - 1, s, cnt);
  30. if (cnt) dfs(u - 1, s + get(u, 1), cnt - 1);
  31. if (cnt + 1 <= u) dfs(u - 1, s + get(u, 2), cnt + 1);
  32. }
  33. int main() {
  34. scanf("%d%d", &n, &m); L = m + 1;
  35. for (int i = 1; i <= n; i++) {
  36. scanf("%s", g[i] + 1);
  37. for (int j = 1; j <= m; j++)
  38. if (g[i][j] == '.') st[i][j] = true, ex = i, ey = j;
  39. }
  40. dfs(L - 1, 0, 0);
  41. f[0][1] = 1;
  42. for (int i = 1; i <= n; i++) {
  43. memset(h, 0, sizeof h);
  44. for (int j = 1; j <= tot; j++)
  45. if (!ask(d[j], L - 1)) h[query(d[j] << 2)] += f[w][j];
  46. memcpy(f[w], h, sizeof h);
  47. for (int j = 1; j <= m; j++) {
  48. w ^= 1; memset(f[w], 0, sizeof f[w]);
  49. for (int u = 1; u <= tot; u++) {
  50. if (!f[!w][u]) continue;
  51. int x = ask(d[u], j - 1), y = ask(d[u], j);
  52. if (!st[i][j]) {
  53. if (!x && !y) add(u, d[u]);
  54. } else if (!x && !y) add(u, d[u] + get(j - 1, 1) + get(j, 2));
  55. else if (!x && y) add(u, d[u]), add(u, d[u] + get(j - 1, y) - get(j, y));
  56. else if (x && !y) add(u, d[u]), add(u, d[u] - get(j - 1, x) + get(j, x));
  57. else if (x == 1 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y) - get(p[u][j], 1));
  58. else if (x == 2 && y == 2) add(u, d[u] - get(j - 1, x) - get(j, y) + get(p[u][j - 1], 1));
  59. else if (x == 2 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y));
  60. else if (x == 1 && y == 2 && i == ex && j == ey && d[u] - get(j - 1, 1) - get(j, 2) == 0) ans += f[!w][u];
  61. }
  62. }
  63. }
  64. printf("%lld\n", ans);
  65. return 0;
  66. }

HNOI2007 神奇游乐园

题意:有权网格图,求回路最大权值。

状态同上面,可以用括号序列维护(两两配对)。

转移稍有不同,每个封口都可以给 \(\text{ans}\) 贡献,另外上题的分类 1 可以考虑不选这个格子。

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. using namespace std;
  5. const int N = 105, M = 7, S = 42000, INF = 0xcfcfcfcf;
  6. int n, m, a[N][M], d[S], tot, w, c[M], p[S][M], now[M], top, s[M], L;
  7. int ans = -2e9, f[2][S], h[S];
  8. int inline query(int x) { return lower_bound(d + 1, d + 1 + tot, x) - d; }
  9. int inline ask(int x, int i) { return x >> (2 * i) & 3; }
  10. int inline get(int i, int t) { return t << (2 * i); }
  11. void inline add(int a, int b, int v) { f[w][query(b)] = max(f[w][query(b)], f[!w][a] + v); }
  12. void inline work(int x) {
  13. memset(now, -1, sizeof now); top = 0;
  14. for (int i = 0; i < 2 * L; i += 2) {
  15. int t = x >> i & 3;
  16. if (t == 1) s[++top] = i >> 1;
  17. else if (t == 2) now[s[top]] = i >> 1, now[i >> 1] = s[top--];
  18. }
  19. d[++tot] = x;
  20. for (int i = 0; i < L; i++) p[tot][i] = now[i];
  21. }
  22. void dfs(int u, int s, int cnt) {
  23. if (u == -1) { if (!cnt) work(s); return ; }
  24. dfs(u - 1, s, cnt);
  25. if (cnt) dfs(u - 1, s + get(u, 1), cnt - 1);
  26. if (cnt + 1 <= u) dfs(u - 1, s + get(u, 2), cnt + 1);
  27. }
  28. int main() {
  29. scanf("%d%d", &n, &m); L = m + 1;
  30. for (int i = 1; i <= n; i++)
  31. for (int j = 1; j <= m; j++)
  32. scanf("%d", &a[i][j]);
  33. dfs(L - 1, 0, 0);
  34. memset(f, 0xcf, sizeof f);
  35. f[0][1] = 0;
  36. for (int i = 1; i <= n; i++) {
  37. memset(h, 0xcf, sizeof h);
  38. for (int j = 1; j <= tot; j++)
  39. if (!ask(d[j], L - 1)) h[query(d[j] << 2)] = max(h[query(d[j] << 2)], f[w][j]);
  40. memcpy(f[w], h, sizeof h);
  41. for (int j = 1; j <= m; j++) {
  42. w ^= 1; memset(f[w], 0xcf, sizeof f[w]);
  43. for (int u = 1; u <= tot; u++) {
  44. if (f[!w][u] == INF) continue;
  45. int x = ask(d[u], j - 1), y = ask(d[u], j);
  46. if (!x && !y) add(u, d[u] + get(j - 1, 1) + get(j, 2), a[i][j]), add(u, d[u], 0);
  47. else if (!x && y) add(u, d[u], a[i][j]), add(u, d[u] + get(j - 1, y) - get(j, y), a[i][j]);
  48. else if (x && !y) add(u, d[u], a[i][j]), add(u, d[u] - get(j - 1, x) + get(j, x), a[i][j]);
  49. else if (x == 1 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y) - get(p[u][j], 1), a[i][j]);
  50. else if (x == 2 && y == 2) add(u, d[u] - get(j - 1, x) - get(j, y) + get(p[u][j - 1], 1), a[i][j]);
  51. else if (x == 2 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y), a[i][j]);
  52. else if (x == 1 && y == 2 && d[u] - get(j - 1, 1) - get(j, 2) == 0) ans = max(ans, f[!w][u] + a[i][j]);
  53. }
  54. }
  55. }
  56. printf("%d\n", ans);
  57. return 0;
  58. }

SCOI2011 地板

题意:用 L 铺满网格图(有障碍物)的方案数。

不需要存连通性,要存每个插头有没有拐弯,三进制状态就可以。

选行列短的当列做,这样状态总数就是 \(\le 3 ^ {11}\) 的。

写了一次三进制,貌似蛮好写的,预处理出来 \(3\) 的幂次(也就是权),这样模拟位运算都是 \(O(1)\) 的。

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. using namespace std;
  5. const int N = 105, M = 12, S = 180000, P = 20110520;
  6. int n, m, ex, ey, ans, Pow[M], f[2][S], w, h[S];
  7. char g[N][N];
  8. bool st[N][N];
  9. int inline ask(int x, int i) { return x / Pow[i] % 3; }
  10. int inline get(int i, int t) { return t * Pow[i]; }
  11. void inline add(int a, int b) { (f[w][b] += f[!w][a]) %= P; }
  12. void inline out(int x) {
  13. for (int i = 0; i <= m; i++) {
  14. cout << (x % 3);
  15. x /= 3;
  16. }
  17. }
  18. int main() {
  19. Pow[0] = 1;
  20. for (int i = 1; i < M; i++) Pow[i] = Pow[i - 1] * 3;
  21. scanf("%d%d", &n, &m);
  22. for (int i = 1; i <= n; i++) scanf("%s", g[i] + 1);
  23. if (m > n) {
  24. for (int i = 1; i <= m; i++)
  25. for (int j = 1; j < i; j++) swap(g[i][j], g[j][i]);
  26. swap(m, n);
  27. }
  28. for (int i = 1; i <= n; i++)
  29. for (int j = 1; j <= m; j++)
  30. if (g[i][j] == '_') ex = i, ey = j, st[i][j] = true;
  31. f[0][0] = 1;
  32. for (int i = 1; i <= n; i++) {
  33. memset(h, 0, sizeof h);
  34. for (int j = 0; j < Pow[m + 1]; j++)
  35. if (!ask(j, m)) (h[j * 3] += f[w][j]) %= P;
  36. memcpy(f[w], h, sizeof h);
  37. for (int j = 1; j <= m; j++) {
  38. w ^= 1, memset(f[w], 0, sizeof f[w]);
  39. for (int u = 0; u < Pow[m + 1]; u++) {
  40. if (!f[!w][u]) continue;
  41. int x = ask(u, j - 1), y = ask(u, j);
  42. if (!st[i][j]) {
  43. if (!x && !y) add(u, u);
  44. } else if (!x && !y) add(u, u + get(j - 1, 2) + get(j, 2)), add(u, u + get(j - 1, 1)), add(u, u + get(j, 1));
  45. else if (x == 0 && y == 1) add(u, u + get(j, 1)), add(u, u - get(j, 1) + get(j - 1, 1));
  46. else if (x == 0 && y == 2) {
  47. add(u, u - get(j, 2) + get(j - 1, 2));
  48. add(u, u - get(j, 2));
  49. if (i == ex && j == ey && u - get(j, 2) == 0) (ans += f[!w][u]) %= P;
  50. } else if (x == 1 && y == 0) add(u, u + get(j - 1, 1)), add(u, u - get(j - 1, 1) + get(j, 1));
  51. else if (x == 1 && y == 1) {
  52. add(u, u - get(j - 1, 1) - get(j, 1));
  53. if (i == ex && j == ey && u - get(j - 1, 1) - get(j, 1) == 0) (ans += f[!w][u]) %= P;
  54. } else if (x == 2 && y == 0) {
  55. add(u, u - get(j - 1, 2) + get(j, 2));
  56. add(u, u - get(j - 1, 2));
  57. if (i == ex && j == ey && u - get(j - 1, 2) == 0) (ans += f[!w][u]) %= P;
  58. }
  59. }
  60. }
  61. }
  62. printf("%d\n", ans);
  63. return 0;
  64. }

学习笔记:插头DP的更多相关文章

  1. [学习笔记]插头dp

    基于连通性的状压dp 巧妙之处:插头已经可以表示内部所有状态了. 就是讨论麻烦一些. 简介 转移方法:逐格转移,分类讨论 记录状态方法:最小表示法(每次要重新编号,对于一类没用“回路路径”之类的题,可 ...

  2. [学习笔记] 数位DP的dfs写法

    跟着洛谷日报走,算法习题全都有! 嗯,没错,这次我也是看了洛谷日报的第84期才学会这种算法的,也感谢Mathison大佬,素不相识,却写了一长篇文章来帮助我学习这个算法. 算法思路: 感觉dfs版的数 ...

  3. [学习笔记]区间dp

    区间 \(dp\) 1.[HAOI2008]玩具取名 \(f[l][r][W/I/N/G]\) 表示区间 \([l,r]\) 中能否压缩成 \(W/I/N/G\) \(Code\ Below:\) # ...

  4. [学习笔记]树形dp

    最近几天学了一下树形\(dp\) 其实早就学过了 来提高一下打开树形\(dp\)的姿势. 1.没有上司的晚会 我的人生第一道树形\(dp\),其实就是两种情况: \(dp[i][1]\)表示第i个人来 ...

  5. 【学习笔记】dp基础

    知识储备:dp入门. 好了,完成了dp入门,我们可以做一些稍微不是那么裸的题了. dp基础,主要是做题,只有练习才能彻底掌握. 洛谷P1417 烹调方案 分析:由于时间的先后会对结果有影响,所以c[i ...

  6. 【学习笔记】dp入门

    知识点 动态规划(简称dp),可以说是各种程序设计中遇到的第一个坎吧,这篇博文是我对dp的一点点理解,希望可以帮助更多人dp入门.   先看看这段话 动态规划(dynamic programming) ...

  7. [学习笔记]动态dp

    其实就过了模板. 感觉就是带修改的dp [模板]动态dp 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y表示修改点x的权值为y. 你需要在每次操作之后求出这棵树的最大权独立集的权值大小 ...

  8. [学习笔记]整体DP

    问题: 有一些问题,通常见于二维的DP,另一维记录当前x的信息,但是这一维过大无法开下,O(nm)也无法通过. 但是如果发现,对于x,在第二维的一些区间内,取值都是相同的,并且这样的区间是有限个,就可 ...

  9. [BZOJ4011][HNOI2015] 落忆枫音(学习笔记) - 拓扑+DP

    其实就是贴一下防止自己忘了,毕竟看了题解才做出来 Orz PoPoQQQ 原文链接 Description 背景太长了 给定一个DAG,和一对点(x, y), 在DAG中由x到y连一条有向边,求生成树 ...

  10. POJ3254Corn Fields (状态压缩or插头DP)

    Description Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; ...

随机推荐

  1. binary hacks读数笔记(dlopen、dlsym、dlerror、dlclose)

    1.dlopen是一个强大的库函数.该函数将打开一个动态库,并把它装入内存.该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的.比如 Apache Web 服务器利用这个函数在运行过程中加载 ...

  2. React native路由跳转navigate、push、replace的区别

    由于没有系统的去学习RN,对路由跳转了解不多,只是跟着项目在做,抽点时间简单学习一下RN路由跳转方法区别,总结如下: 如上图,外部是一个栈容器,此时A页面在最底部,navigate到B页面,为什么此时 ...

  3. elasticsearch集群安装+安全验证+kibana安装

    准备环境 启动4个centos容器, 并暴露相对应端口 (我的本机ip为172.16.1.236,以下涉及到的地方需要修改为自己的ip) node_name ip http port transpor ...

  4. [前端web安全]XSS漏洞基础入门

    前言 XSS漏洞 Xss(Cross-Site Scripting)意为跨站脚本攻击,为了不和层叠样式表(Cascading Style Sheets,CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS ...

  5. sqlilab less11-less18

    less-11 uname和passwd直接带入查询,万能密码 sqlmap自动搜索表单,或者抓包后用-r参数 less-12 post数据用小括号进行包裹,构造万能密码") or 1=1 ...

  6. Linux Command Line_1_Shell基础

      引言 图形用户界面(GUI)让简单的任务更容易完成,命令行界面(CLI)使完成复杂的任务成为可能. 第一部分:Shell 本部分包括命令行基本语言,命令组成结构,文件系统浏览.编写命令行.查找命令 ...

  7. 如何制作C语言基本数据类型的思维导图

    在使用C语言编写程序时,数据类型是一个非常重要的内容,任何一个不被重视的数据错误都会使编译器无法翻译,导致程序报错. 使用思维导图来梳理各个数据类型是一个很有效的记忆方法,接下来就为大家展示一下我用i ...

  8. 如何使用Camtasia制作动态动画场景?

    也许在学习编辑视频的你知道Camtasia 2019(win系统),知道Camtasia的视频编辑功能,录制屏幕功能,但你可能想不到,Camtasia还可以制作动态动画场景.跟我一起学习一下吧! 一. ...

  9. 如何使用ABBYY FineReader处理文档图像的缺陷?

    通过扫描仪或者数码相机获取的图像文件,容易出现文本扭曲.页面歪斜等缺陷,会影响到OCR的识别质量.此时,用户可使用ABBYY FineReader 15(Windows系统)OCR文字识别软件的自动和 ...

  10. 宝塔Linux面板基础命令

    安装宝塔Centos安装脚本 yum install -y wget && wget -O install.sh http://download.bt.cn/install/insta ...