【luogu P2595】多米诺骨牌(插头DP)(容斥)

多米诺骨牌

题目链接:luogu P2595

题目大意

给你一个 n*m 的矩阵,你可以横竖放 1*2 的木块。
问你有多少种方法,使得每相邻的两行之间必定有横跨的木块,每相邻的两列之间必有横跨的木块。
不是一定要全部放满木块。

思路

首先看到这种题不难想到插头 DP。
但是你发现它那个要求很烦。
那你先不要管他,不难预处理出 gx1,x2,y1,y2 为区间 [x1,y1][x2,y2] 随便放的方案数。
(看似是 n3m22m,但我们其实可以在跑的过程中压掉一维,变成 n2m22m

然后我们考虑去搞它的要求。
那发现还是不会搞,那我们考虑反过来,求全部的减去有不横跨的。
那不横跨的其实很简单,你把两个 g 拼在一起,得到的方案数是两个相乘,就可以保证拼接的地方不可能有横跨了。
那不难想到可以枚举分割的状态,但是你两个维度搞在一起还是会超时。

然后你就考虑一个维度直接暴力枚举状态,然后另一个维度用 DP 来搞。
怎么 DP 呢,大概是设 Fi 为前 i 部分分割好了的方案数,然后就 n2 转移。(然后转移的时候注意原来只有一段的情况,然后也容斥一下)

然后这么搞就好了。
我的代码常数大,要卡一卡时间,然后一开始预处理 g 的插头 DP 其实还可以滚动数组,但我没用。
(我直接开 O2 了)

代码

#pragma GCC optimize(2) #include<cstdio> #include<cstring> #define mo 19901013 #define rr register #define ll long long using namespace std; int n, m, nxt, a[16], xx, yy, nn, mm; int g[16][16][16][16], f[16][16][70001], ans; int sum, F[16]; bool cn[16][16]; char c; void slove(int x1, int y1, int y2) {//求出一个区间任意放的方案 nn = n - x1 + 1; mm = y2 - y1 + 1; f[0][0][(1 << mm) - 1] = 1; for (int x = 0; x < nn; x++) for (int y = 0; y < mm; y++) { if (y == mm - 1) xx = x + 1, yy = 0; else xx = x, yy = y + 1; for (rr int k = 0; k < (1 << mm); k++) { if (!cn[x + x1][y + y1]) f[xx][yy][k | (1 << y)] = (f[xx][yy][k | (1 << y)] + f[x][y][k]) % mo; else { nxt = k; if ((nxt >> y) & 1) nxt ^= (1 << y); f[xx][yy][nxt] = (f[xx][yy][nxt] + f[x][y][k]) % mo; if (!((k >> y) & 1)) f[xx][yy][k | (1 << y)] = (f[xx][yy][k | (1 << y)] + f[x][y][k]) % mo; if (y > 0 && !((k >> (y - 1)) & 1)) f[xx][yy][k | (1 << y) | (1 << (y - 1))] = (f[xx][yy][k | (1 << y) | (1 << (y - 1))] + f[x][y][k]) % mo; } } } for (int i = x1; i <= n; i++) for (rr int k = 0; k < (1 << mm); k++) g[x1][i][y1][y2] = (g[x1][i][y1][y2] + f[i - x1 + 1][0][k]) % mo; for (int i = 0; i <= nn; i++) for (int j = 0; j < mm; j++) for (rr int k = 0; k < (1 << mm); k++) f[i][j][k] = 0; } int main() { scanf("%d %d", &n, &m); for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) { c = getchar(); while (c != 'x' && c != '.') c = getchar(); if (c == '.') cn[i][j] = 1; else cn[i][j] = 0; } for (int x1 = 1; x1 <= n; x1++) for (int y1 = 1; y1 <= m; y1++) for (int y2 = y1; y2 <= m; y2++) { slove(x1, y1, y2); } for (int k = 0; k < (1 << (m - 1)); k++) { memset(F, 0, sizeof(F)); a[0] = 0; a[++a[0]] = 0;//搞出这个状态对应的每个分割位 for (int i = 0; i < m - 1; i++) if ((k >> i) & 1) a[++a[0]] = i + 1; a[++a[0]] = m; for (int i = 1; i <= n; i++) {//另外一个维度用 n^2 DP for (int j = 1; j <= i; j++) { sum = 1; for (int x = 2; x <= a[0]; x++) { sum = (1ll * sum * g[j][i][a[x - 1] + 1][a[x]]) % mo; } if (j == 1) F[i] = sum;//容斥 else F[i] = (F[i] - (1ll * sum * F[j - 1]) % mo + mo) % mo; } } if (a[0] & 1) ans = (ans - F[n] + mo) % mo; else ans = (ans + F[n]) % mo;//容斥 } printf("%d\n", ans); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P2595.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(39)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示