Luogu P2595 [ZJOI2009]多米诺骨牌 容斥,枚举,插头dp,轮廓线dp
真的是个好(毒)题(瘤)。其中枚举的思想尤其值得借鉴。
\(40pts\):插头\(dp\),记录插头的同时记录每一列的连接状况,复杂度\(O(N*M*2^{n + m} )\)。
\(100pts\):容斥\(+\)插头\(/\)轮廓线。目前要维护每两行和每两列的限制,我们把两个限制分开讨论。预处理一下每个子矩阵如果不作限制的可行方案,然后人为地进行限制分割。对于列的限制用容斥解决,对于行的限制套在里面逐行枚举做一次\(dp\),就可以把复杂度降到可以接受的\(O(m^3*2^n)\)。
代码有借鉴网上。并非完全原创。
\(40pts\):
#include <bits/stdc++.h>
using namespace std;
const int N = 15 + 5;
const int base = 2999830;
const int Mod = 19901013;
const int M = 3000000 + 5;
typedef unsigned int uint;
int n, m, cur, las, cnt[2], can[N][N];
int nxt[M], head[M], dp[2][M]; uint Hash[2][M];
int get_wei (uint zt, int wei) {
return (zt >> wei) % 2;
}
int alt_wei (uint zt, int wei, int val) {
return zt + ((0ll + val - get_wei (zt, wei)) << wei);
}
void update (uint zt, int val) {
uint _zt = zt % base;
for (int i = head[_zt]; i; i = nxt[i]) {
if (Hash[cur][i] == zt) {
(dp[cur][i] += val) %= Mod; return;
}
}
nxt[++cnt[cur]] = head[_zt];
head[_zt] = cnt[cur];
Hash[cur][cnt[cur]] = zt;
// cout << "dp[" << cur << "][" << cnt[cur] << "] = " << val << endl;
dp[cur][cnt[cur]] = val;
}
int get_val (uint zt) {
uint _zt = zt % base;
for (int i = head[_zt]; i; i = nxt[i]) {
if (Hash[cur][i] == zt) {
return dp[cur][i];
}
}
return 0;
}
void change_row () {
for (int i = 1; i <= cnt[cur]; ++i) {
uint &zt = Hash[cur][i];
if (get_wei (zt, m + m) == 0) {
dp[cur][i] = 0;//到不了下一行
}
for (int i = m; i >= 1; --i) {
zt = alt_wei (zt, i, get_wei (zt, i - 1));
}
zt = alt_wei (zt, 0, 0); // 全都向后移一位
zt = alt_wei (zt, m + m, 0); // 到下一行的标记清空
}
}
int solve () {
update (1 << (m + m), 1);
for (int i = 1; i <= n; ++i) {
change_row ();
for (int j = 1; j <= m; ++j) {
// cout << "i = " << i << " j = " << j << endl;
las = cur, cur ^= 1, cnt[cur] = 0;
memset (head, 0, sizeof (head));
for (int k = 1; k <= cnt[las]; ++k) {
uint zt = Hash[las][k];
int b1 = get_wei (zt, j - 1);
int b2 = get_wei (zt, j - 0);
int val = dp[las][k];
if (val == 0) continue; // 不转移的小优化
// cout << "b1 = " << b1 << " b2 = " << b2 << endl;
if (!can[i][j]) {
if (!b1 && !b2) {
update (zt, val);
}
} else {
if (b1 == 0 && b2 == 0) {
if (can[i][j + 1]) {
uint _zt = zt;
_zt = alt_wei (_zt, j - 0, 1); // 右插头改为 1
_zt = alt_wei (_zt, m + j, 1); // j 列和 j + 1 列相连
update (_zt, val);
}
if (can[i + 1][j]) {
uint _zt = zt;
_zt = alt_wei (_zt, j - 1, 1); // 下插头改为 1
_zt = alt_wei (_zt, m + m, 1); // 和下一行连通
update (_zt, val);
}
update (zt, val); // 注意你并不需要放满所有没有障碍的格子
}
if (b1 + b2 == 1) { // 有一个连入的插头
uint _zt = zt;
_zt = alt_wei (_zt, j - 0, 0);
_zt = alt_wei (_zt, j - 1, 0);
update (_zt, val);
}
}
}
// cout << "las : " << endl;
// for (int k = 1; k <= cnt[las]; ++k) {
// cout << "state = " << Hash[las][k] << " val = " << dp[las][k] << endl;
// }
// cout << "cur : " << endl;
// for (int k = 1; k <= cnt[cur]; ++k) {
// cout << "state = " << Hash[cur][k] << " val = " << dp[cur][k] << endl;
// }
// cout << endl;
}
}
uint fin = 0;
for (int i = m + 1; i < m + m; ++i) {
fin |= (1 << i);
}
return get_val (fin);
}
int main () {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int ch = getchar ();
while (ch != '.' && ch != 'x') {
ch = getchar ();
}
if (ch == '.') {
can[i][j] = true;
}
}
}
// for (int i = 1; i <= n; ++i) {
// for (int j = 1; j <= m; ++j) {
// cout << can[i][j] << " ";
// }
// cout << endl;
// }
cout << solve () << endl;
}
\(100pts\):
#include <bits/stdc++.h>
using namespace std;
const int N = 15 + 5;
const int M = 40000 + 5;
const int Mod = 19901013;
int n, m, top, ans;
int f[N], num[N], tmp[2][M], dp[N][N][N][N];
bool mp[N][N];
int ask (int u, int v) {
return (u >> v) % 2;
}
void work (int w, int u, int v) {
int las = 0, tot = (1 << (v - u + 1)) - 1;
for (int i = 0; i <= tot; ++i) {
tmp[0][i] = 0;
}
int t2 = 0;
for (int i = u; i <= v; ++i) {
if (mp[w][i]) {
t2 |= (1 << (i - u));
}
}
tmp[0][t2] = 1;
for (int i = w + 1; i <= m + 1; ++i) {
int zt = 0;
for (int j = u, t = 0; j <= v; ++j, ++t) {
zt |= (mp[i][j] << t);
int cur = las ^ 1;
for (int k = 0; k <= tot; ++k) tmp[cur][k] = 0;
for (int k = 0; k <= tot; ++k) {
if(!tmp[las][k]) continue;
int t2 = k;
if (ask(t2,t) != mp[i][j]) {
t2 ^= (1 << t);
}
(tmp[cur][t2] += tmp[las][k]) %= Mod;
if (ask (k, t)) continue;
if (j != v && !ask (k, t + 1)) {
t2 = k | (1 << (t + 1));
if (ask (t2, t) != mp[i][j]) {
t2 ^= (1 << t);
}
(tmp[cur][t2] += tmp[las][k]) %= Mod;
}
if(!mp[i][j]) {
t2 = k | (1 << t);
(tmp[cur][t2] += tmp[las][k]) %= Mod;
}
}
las = cur;
}
dp[w][u][i - 1][v] = tmp[las][zt];
}
}
int main () {
cin >> m >> n;
for(int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
int ch = getchar ();
while (ch != '.' && ch != 'x') {
ch = getchar ();
}
mp[i][j] = (ch == 'x');
}
}
for (int i = 1; i <= n; ++i) {
for (int j = i; j <= n; ++j) {
for (int k = 1; k <= m; ++k) {
work (k, i, j);
}
}
}
int tot = (1 << (n - 1)) - 1;
for(int i = 0; i <= tot; ++i) {
int top = 1;
num[top] = 0;
for (int j = 0; j < n - 1; ++j) {
if (ask (i, j)) num[++top] = j + 1;
}
num[++top] = n;
for (int d = 1; d <= m; ++d) {
for (int u = 1; u <= d; ++u) {
int t = 1;
for(int j = 2; j <= top; ++j) {
int p = num[j - 1] + 1, q = num[j];
t = (1ll * t * dp[u][p][d][q]) % Mod;
}
if (u == 1) {
f[d] = t;
} else {
f[d] = (0ll + f[d] - (1ll * t * f[u - 1])) % Mod;
}
}
}
(f[m] += Mod) %= Mod;
if (top % 2 == 1) {
(ans -= f[m]) %= Mod;
} else {
(ans += f[m]) %= Mod;
}
}
cout << (ans + Mod) % Mod << endl;
}
Luogu P2595 [ZJOI2009]多米诺骨牌 容斥,枚举,插头dp,轮廓线dp的更多相关文章
- 51nod 1518 稳定多米诺覆盖(容斥+二项式反演+状压dp)
[传送门[(http://www.51nod.com/Challenge/Problem.html#!#problemId=1518) 解题思路 直接算不好算,考虑容斥,但并不能把行和列一起加进去容斥 ...
- 【做题】51NOD1518 稳定多米诺覆盖——容斥&dp
题意:求有多少种方案,用多米诺骨牌覆盖一个\(n\times m\)的棋盘,满足任意一对相邻行和列都至少有一个骨牌横跨.对\(10^9+7\)取模. \(n,m \leq 16\) 首先,这个问题的约 ...
- 「ZJOI2009」多米诺骨牌
「ZJOI2009」多米诺骨牌 题目描述 有一个n × m 的矩形表格,其中有一些位置有障碍.现在要在这个表格内 放一些1 × 2 或者2 × 1 的多米诺骨牌,使得任何两个多米诺骨牌没有重叠部分,任 ...
- 【Tsinghua OJ】多米诺骨牌(domino)问题
(domino.c/cpp)[问题描述] 小牛牛对多米诺骨牌有很大兴趣,然而她的骨牌比较特别,只有黑色和白色的两种.她觉 得如果存在连续三个骨牌是同一种颜色,那么这个骨牌排列便是不美观的.现在她有n个 ...
- 省选训练赛第4场D题(多米诺骨牌)
题目来自FZU2163 多米诺骨牌 Time Limit: 1000 mSec Memory Limit : 32768 KB Problem Description Vasya很喜欢排多米诺 ...
- 【01背包】洛谷P1282多米诺骨牌
题目描述 多米诺骨牌有上下2个方块组成,每个方块中有1~6个点.现有排成行的 上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|.例如在图8-1中,S1=6+1+1+1=9, ...
- 多米诺骨牌放置问题(状压DP)
例题: 最近小A遇到了一个很有趣的问题: 现在有一个\(n\times m\)规格的桌面,我们希望用\(1 \times 2\)规格的多米诺骨牌将其覆盖. 例如,对于一个\(10 \times 11\ ...
- P1282 多米诺骨牌 (背包变形问题)
题目描述 多米诺骨牌有上下2个方块组成,每个方块中有1~6个点.现有排成行的 上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|.例如在图8-1中,S1=6+1+1+1=9, ...
- [LeetCode] Push Dominoes 推多米诺骨牌
There are N dominoes in a line, and we place each domino vertically upright. In the beginning, we si ...
随机推荐
- Python数据分析中Groupby用法之通过字典或Series进行分组
在数据分析中有时候需要自己定义分组规则 这里简单介绍一下用一个字典实现分组 people=DataFrame( np.random.randn(5,5), columns=['a','b','c',' ...
- flutter block回调
block回调在oc中很常见,到了flutter中仍然有block回调 自定义一个StatefulWidget PageTitle 无参数回调VoidCallback VoidCallback onT ...
- ORACLE官方全托管驱动 Oracle.ManagedDataAccess 12.1.0.1.0
以前用Oracle的时候,必须得装他臃肿的客户端,网上虽然也有提供直连Oracle的驱动,但也是要收费的,最近Oracle终于开窍了,提供了官方的全托管驱动. 这次是随Oracle ODAC 12c ...
- js获取当天时间,7天前后时间,时间格式化
格式化时间年月日时分秒 //时间戳转换方法 date:时间戳数字 formatDate(date) { var date = new Date(date); var YY = date.getFull ...
- LeetCode.1005-K次取负数组和最大(Maximize Sum Of Array After K Negations)
这是悦乐书的第376次更新,第403篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第237题(顺位题号是1005).给定一个整数数组A,我们必须按以下方式修改数组:我们选 ...
- java中String中的endsWith()方法
解释:endsWith() ——此方法测试字符串是否以指定的后缀 suffix 结束. 此方法的定义:public boolean endsWith(String suffix) 我这里判断的是路径是 ...
- 微信小程序--catchtap&bindtap
转自:https://www.cnblogs.com/heron-yu/p/7244481.html 转自:http://blog.csdn.net/xiaochun365/article/detai ...
- python 并发编程 多线程 守护线程
做完工作这个进程就应该被销毁 单线程情况: 一个进程 ,默认有一个主线程 ,这个主线程执行完代码后 ,就应该自动销毁.然后进程也销毁. 多线程情况: 主线程代表进程结束 一个进程可以开多个线程,默认开 ...
- c++嵌入linux指令以查找文件夹
char buf[256]={0}; char cmd[64] ={0}; FILE *fp=NULL; snprintf(cmd,sizeof(cmd),"ls %s",&quo ...
- C语言作业11
问题 答案 这个作业属于那个课程 C语言程序设计 这个作业要求在哪里 https://www.cnblogs.com/galen123/p/11996995.html 我在这个课程的目标是 在学好C语 ...