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 ...
随机推荐
- emqtt emq 的用户密码认证
MQTT 认证设置 EMQ 消息服务器认证由一系列认证插件(Plugin)提供,系统支持按用户名密码.ClientID 或匿名认证. 系统默认开启匿名认证(anonymous),通过加载认证插件可开启 ...
- 二十:jinja2之加载静态文件
静态文件: flask默认指定的静态文件路径为根目录下的static,可以自定义路径,并指定,使用url_for('文件夹', filename='文件名')引用 加载css文件 加载js文件 其他文 ...
- 线性模型-线性回归、Logistic分类
线性模型是机器学习中最简单的,最基础的模型结果,常常被应用于分类.回归等学习任务中. 回归和分类区别: 回归:预测值是一个连续的实数: 分类:预测值是离散的类别数据. 1. 线性模型做回归任务 ...
- Integer类的常量池
- numpy伪随机数的生成
numpy伪随机数的生成 normal函数 可以用normal来得到一个标准正态分布的4×4样本数组 >>> import numpy as np >>> samp ...
- CF 1136B Nastya Is Playing Computer Games
题目链接:codeforces.com/problemset/problem/1136/B 题目分析 首先,读完题目,看了是个B题,嗯嗯...... 果断找规律,然后交了一波,居然过了!!! 代码区 ...
- js知识点——1
onload 事件会在页面或图像加载完成后立即发生. document.write("内容")将在加载页面时输出 内容可以是什么,可以是一个标签,它输出的文件不能自动换行: < ...
- Select 和Alert
Select 和Alert使用前都必须先导入 from selenium.webdriver.common.alert import Alert from selenium.webdriver.sup ...
- CSS 相对定位 绝对定位
css中的相对定位和绝对定位. 定位标签:position 包含属性:relative(相对) absolute(绝对) 1.position:relative; 如果对一个元素进行相对定位,首先它将 ...
- homebrew学习(四)之取消homebrew自动更新
homebrew自动更新 使用brew install /brew cask install安装软件总是先updating HomeBrew…,速度很慢 取消homebrew自动更新 方法一:使用命令 ...