「ZJOI2009」多米诺骨牌

题目描述

有一个n × m 的矩形表格,其中有一些位置有障碍。现在要在这个表格内 放一些1 × 2 或者2 × 1 的多米诺骨牌,使得任何两个多米诺骨牌没有重叠部分,任何一个骨牌不能放到障碍上。并且满足任何相邻两行之间都有至少一个骨牌横跨,任何相邻两列之间也都至少有一个骨牌横跨。求有多少种不同的放 置方法,注意你并不需要放满所有没有障碍的格子。\(n, m \leq 15\)

解题思路 :

先考虑没有至少一个骨牌横跨这个限制该怎么做,只需要轮廓线 \(\text{dp}\) 记录一下轮廓线上的格子有没有放每次分讨缺口进行转移即可。如果只有行有限制也比较好做,只需要轮廓线的时候再多记一维来钦定必须要横跨即可,或者枚举哪些列没有横跨,可以通过二项式反演(容斥)得到答案。

于是根据上面两个思路口胡了一个非常 \(\text{Naive}\) 的做法并且成功狗带了,最初的做法每次预处理出每一个子矩阵行必须要跨过的答案,暴力枚举哪些列不跨过容斥。错误的地方在于可能有一个子矩阵并不需要马上让行被跨过,可以在枚举的同一行的另外一个子矩阵被跨过。

这里挂掉后就发现不得不对行列都进行容斥了,考虑暴力容斥的复杂度是 \(O(4^nn^2)\) 显然不能过。但是观察发现一旦行确定好了,列的分割线怎么枚举关于行的贡献都不会变,所以可以暴力枚举行再额外用一个 \(\text{dp}\) 维护列的贡献。显然系数只于选的列的数量有关,可以得到转移方程 \(dp[i] = \sum_{j<i} -dp[j] \times calc(j+1, i, s)\) 。其中 \(calc(j+1, i, s)\) 表示 \([j+1, i]\) 这一块被当前枚举的行集合 \(s\) 划分后的总方案数,这里可以通过轮廓线 \(\text{dp}\) 预处理出每一块子矩阵任意放的方案数来计算,复杂度 \(O(n)\) 。所以用 \(\text{dp}\) 维护后复杂度变成了 \(O(2^n n^2)\) ,足以通过此题。

​其实本题主要的复杂度瓶颈是在预处理部分,由于障碍的存在,不得不预处理出 \(pre[x1][x2][y1][y2]\) 表示这个子矩阵不考虑限制的摆放方案,由于一次轮廓线 \(\text{dp}\) 可以通过 \(O(2^nn^2)\) 的复杂度算出以一条横着的扫描线向下推出的所有子矩阵的值。直接看的话复杂度是 \(O(2^nn^5)\) ,但是观察发现对于每一行的横着的扫描线,每一种长度要算的都是 \(O(n)\) 级别的,而 \(\sum_{i=1}^n2^i = 2^{n+1} - 1\) ,所以实际上一行枚举的集合大小之和只有 \(2^{n+1}\) ,总复杂度就是 \(O(n^32^{n+1})\) ,当然也可以直接暴力出奇迹

/*program by mangoyang*/
#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int f = 0, ch = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
#define Rint register int
const int N = 16, S = (1 << 15) + 5, mod = 19901013;
char mp[N][N];
int f[2][N][S], pre[N][N][N][N], dp[N], val[N], ans, n, m;
inline void up(int &x, int y){ x += y; if(x >= mod) x -= mod; }
inline void gao(int w, int h, int x, int y){
for(Rint o = 0; o < 2; o++)
for(Rint i = 0; i <= h; i++)
for(Rint s = 0; s < (1 << h); s++) f[o][i][s] = 0;
f[0][h][(1<<h)-1] = 1;
for(Rint i = 1, o = 1; i <= w; i++, o ^= 1){
for(Rint s = 0; s < (1 << h); s++) f[o][0][s] = f[o^1][h][s];
for(Rint j = 0; j <= h; j++)
for(Rint s = 0; s < (1 << h); s++) f[o^1][j][s] = 0;
for(Rint j = 0; j < h; j++)
for(Rint s = 0; s < (1 << h); s++){
if(mp[i+x-1][j+y] == 'x'){
up(f[o][j+1][s|(1<<j)], f[o][j][s]); continue;
}
if(!((1 << j) & s)) up(f[o][j+1][s|(1<<j)], f[o][j][s]);
if(j && (!((1 << j - 1) & s)))
up(f[o][j+1][s|(1<<j-1)|(1<<j)], f[o][j][s]);
up(f[o][j+1][s&(~(1<<j))], f[o][j][s]);
}
for(Rint s = 0; s < (1 << h); s++)
up(pre[x][i+x-1][y][h+y-1], f[o][h][s]);
}
}
inline int calc(int x, int y, int s){
int ls = 0, res = 1;
for(int i = 0; i < n - 1; i++) if((1 << i) & s)
res = 1ll * res * pre[ls+1][i+1][x][y] % mod, ls = i + 1;
res = 1ll * res * pre[ls+1][n][x][y] % mod;
return res;
}
inline void addvalue(int s){
memset(dp, 0, sizeof(dp)), dp[0] = -1;
for(int i = 1; i <= m; i++)
for(int j = 0; j < i; j++)
up(dp[i], (1ll * -dp[j] * calc(j + 1, i, s) % mod + mod) % mod);
up(ans, (__builtin_popcount(s) & 1 ? -1 : 1) * dp[m]);
}
signed main(){
read(n), read(m);
for(int i = 1; i <= n; i++) scanf("%s", mp[i] + 1);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
for(int k = j; k <= m; k++) gao(n - i + 1, k - j + 1, i, j);
for(int i = 0; i < (1 << n - 1); i++) addvalue(i);
cout << (ans % mod + mod) % mod;
return 0;
}

「ZJOI2009」多米诺骨牌的更多相关文章

  1. 【Tsinghua OJ】多米诺骨牌(domino)问题

    (domino.c/cpp)[问题描述] 小牛牛对多米诺骨牌有很大兴趣,然而她的骨牌比较特别,只有黑色和白色的两种.她觉 得如果存在连续三个骨牌是同一种颜色,那么这个骨牌排列便是不美观的.现在她有n个 ...

  2. 省选训练赛第4场D题(多米诺骨牌)

    题目来自FZU2163 多米诺骨牌 Time Limit: 1000 mSec    Memory Limit : 32768 KB  Problem Description Vasya很喜欢排多米诺 ...

  3. 【01背包】洛谷P1282多米诺骨牌

    题目描述 多米诺骨牌有上下2个方块组成,每个方块中有1~6个点.现有排成行的 上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|.例如在图8-1中,S1=6+1+1+1=9, ...

  4. 多米诺骨牌放置问题(状压DP)

    例题: 最近小A遇到了一个很有趣的问题: 现在有一个\(n\times m\)规格的桌面,我们希望用\(1 \times 2\)规格的多米诺骨牌将其覆盖. 例如,对于一个\(10 \times 11\ ...

  5. P1282 多米诺骨牌 (背包变形问题)

    题目描述 多米诺骨牌有上下2个方块组成,每个方块中有1~6个点.现有排成行的 上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|.例如在图8-1中,S1=6+1+1+1=9, ...

  6. [LeetCode] Push Dominoes 推多米诺骨牌

    There are N dominoes in a line, and we place each domino vertically upright. In the beginning, we si ...

  7. P1282 多米诺骨牌

    P1282 多米诺骨牌 题目描述 多米诺骨牌有上下2个方块组成,每个方块中有1~6个点.现有排成行的 上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|.例如在图8-1中,S ...

  8. [Luogu1282]多米诺骨牌(DP)

    #\(\color{red}{\mathcal{Description}}\) \(Link\) 我们有一堆多米诺骨牌,上下两个部分都有点数,\(But\)我们有一个操作是可以对调上下的点数.若记一块 ...

  9. 洛谷P1282 多米诺骨牌 (DP)

    洛谷P1282 多米诺骨牌 题目描述 多米诺骨牌有上下2个方块组成,每个方块中有1~6个点.现有排成行的 上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|.例如在图8-1中 ...

随机推荐

  1. 【BZOJ】4873: [Shoi2017]寿司餐厅

    [题目]#2146. 「SHOI2017」寿司餐厅 [题意]给定n种寿司的代号,取区间[i,j]的寿司收益是d[i,j]和所有子区间的d,吃了c(c>0)种代号x的寿司的代价是mx^2+cx,给 ...

  2. 【Atcoder】ARC088 D - Wide Flip

    [题目]D - Wide Flip [题意]给定n个数字的01序列,要求每次翻转>=k个数字使得全0,求最大的k.n<=10^5 [算法]数学 [题解]有两个角度可以得到等价的结论: 1. ...

  3. 论文里有公式?用texlive+texstudio(windows下)

    要写论文了,但论文里有一大堆公式,感觉很麻烦,经过询问同学知道有tex这么个东西,可以像写代码一样写论文,许多论文的格式都有相关的模板,所以学习一下,这里记录一下环境安装. texlive和texst ...

  4. 【leetcode 简单】第三十七题 相交链表

    编写一个程序,找到两个单链表相交的起始节点. 例如,下面的两个链表: A: a1 → a2 ↘ c1 → c2 → c3 ↗ B: b1 → b2 → b3 在节点 c1 开始相交. 注意: 如果两个 ...

  5. 【leetcode 简单】 第八题 删除排序数组中的重复项

    给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成. 示例 1 ...

  6. Linux必备工具Tmux

    之前介绍了Linux的Screen命令,今天介绍一个更为强大的终端工具Tmux. Tmux 是一个用于在一个终端窗口中运行多个终端会话的工具.它基本能替代nohup以及screen,甚至比它们更为强大 ...

  7. 混合式App开发 Apicloud 官方iPhone X 适配

    iPhone X 适配 由于iPhone X的特殊造型,为了方便开发者对iPhone X进行适配,苹果在iOS 11中引入了Safe Area的概念,引擎也在api对象下添加了safeArea属性和s ...

  8. 大数据系列之Flume+kafka 整合

    相关文章: 大数据系列之Kafka安装 大数据系列之Flume--几种不同的Sources 大数据系列之Flume+HDFS 关于Flume 的 一些核心概念: 组件名称     功能介绍 Agent ...

  9. POJ2528 Mayor's posters(线段树染色问题+离散化)

    题目大意:有t组数据,每组数据给你n张海报(1<=n<=10000),下面n组数据分别给出每张海报的左右范围(1 <= l <= r <= 10000000),下一张海报 ...

  10. jekyll简单使用

    jekyll build # => 当前文件夹中的内容将会生成到 ./site 文件夹中. jekyll build –destination <destination> # =&g ...