Description 有一个n×m的矩形表格,其中有一些位置有障碍。现在要在这个表格内放一些1×2或者2×1的多米诺骨牌,使得任何两个多米诺骨牌没有重叠部分,任何一个骨牌不能放到障碍上。并且满足任何相邻两行之间都有至少一个骨牌横跨,任何相邻两列之间也都至少有一个骨牌横跨。求有多少种不同的放置方法,注意你并不需要放满所有没有障碍的格子。 Input 第一行两个整数n;m。接下来n 行,每行m 个字符,表示这个矩形表格。其中字符“x” 表示这个位置有障碍,字符“.” 表示没有障碍。 Output 一行一个整数,表示不同的放置方法数mod 19901013 的值。 Sample Input 3 3 ... ... ... Sample Output 2 Hint 两种放置方法分别为 112 411 4.2 4.2 433 332 注意这里的数字只用于区分骨牌,不同的排列并不代表不同的方案。 【数据范围】 对于40% 的数据,满足1 ≤ n;m ≤ 8。 对于90% 的数据,满足1 ≤ n;m ≤ 14。 对于100% 的数据,满足1 ≤ n;m ≤ 15。
被这道题虐了一上午……
首先考虑一个更简单一些的情况:
若没有“任何相邻两行、列之间都至少有一个骨牌横跨”的限制,那么问题就相当简单了(直接一个裸的状压DP就好了);
若只有行(任意两行之间)的跨立没有列的跨立,当然也很简单(用插头DP实现,保证每行结束都至少有一个插头指向下一行)。
再回到原问题中。
一种想法就是增加状态的信息,在转移的时候将相邻各列的相连情况记录下来,解决了列的横跨问题;并且用上面所说的方法解决行的横跨问题。(下面附代码。)这样做的复杂度是O(n·m·4^m),显然要超时。
所以需要继续寻找其他办法。
通常,我们很容易计算某两行(列)之间一定没有骨牌横跨的情况(只需把原问题分解为几个小的部分再把分别的结果相乘即可),所以这令人想到了容斥原理!
首先一个预处理(用状压DP实现),算出ini[U][D][L][R]即在U <= i < D, L <= j < R的矩形中随便放骨牌的总数。
接下来再使用容斥原理计算最终结果。
我们先只考虑列,就拿样例来说:设|A|={1, 2列中有骨牌横跨的情况的总数},|B|={2, 3列中有骨牌横跨的情况的总数}。
则有:
那么,我们就可以枚举中间的分割线(即规定某些列不能被横跨),对于每一个容斥项(我们暂时把容斥原理公式中的每一项成为容斥项),又在行上进行动态规划(用一个g数组,令g[i]为这个容斥项中的前i行中每相邻两行都有骨牌横跨的总数),最后将结果正负交错地累加起来即可。
Accode:
#include <cstdio> #include <cstdlib> #define Add(a, b) ((a) += (b)) %= MOD typedef long long int64; const int maxN = 20, MOD = 19901013; const int maxSTATUS = 1 << 16; int64 g[maxN], ans, tmp; char mp[maxN][maxN]; int f[maxN][maxN][maxSTATUS]; int ini[maxN][maxN][maxN][maxN], c[maxN]; int n, m, pst, ths; int (*f0)[maxSTATUS], (*f1)[maxSTATUS]; int main() { freopen("domino.in", "r", stdin); freopen("domino.out", "w", stdout); scanf("%d%d\n", &n, &m); for (int i = 0; i < n; ++i) gets(mp[i]); for (int L = 0; L < m; ++L) for (int R = L + 1; R < m + 1; ++R) for (int U = 0; U < n; ++U) { int Lim = 1 << (R - L); for (int k = 1; k < Lim; ++k) f[U][L][k] = 0; f[U][L][0] = 1; for (int i = U; i < n; ++i) { for (int j = L; j < R; ++j) { f0 = &f[i][j]; if (j < R - 1) f1 = &f[i][j + 1]; else f1 = &f[i + 1][L]; for (int k = 0; k < Lim; ++k) (*f1)[k] = 0; int x = R - j - 1; if (mp[i][j] == 'x') for (int k = 0; k < Lim; ++k) Add((*f1)[k & ~(1 << x)], (*f0)[k]); else for (int k = 0; k < Lim; ++k) { if (!((*f0)[k])) continue; Add((*f1)[k & ~(1 << x)], (*f0)[k]); if (j > L && mp[i][j - 1] - 'x' && !(k & (2 << x))) Add((*f1)[k | (3 << x)], (*f0)[k]); if (i > U && mp[i - 1][j] - 'x' && !(k & (1 << x))) Add((*f1)[k | (1 << x)], (*f0)[k]); } } for (int k = 0; k < Lim; ++k) Add(ini[U][i + 1][L][R], (*f1)[k]); } } for (int S = 0; S < 1 << (m - 1); ++S) { int tot = 0; c[tot++] = 0; for (int i = 0; i < m - 1; ++i) if (S & (1 << i)) c[tot++] = i + 1; c[tot] = m; for (int D = 1; D < n + 1; ++D) for (int U = 0; U < D; ++U) { tmp = ini[U][D][c[0]][c[1]]; for (int i = 1; i < tot; ++i) (tmp *= ini[U][D][c[i]][c[i + 1]]) %= MOD; //这里千万不能用减法代替除法取余, //否则后果自负(在时限1000s的情况下都能超时)。 if (!U) g[D - 1] = tmp; else Add(g[D - 1], -tmp * g[U - 1]); } tmp = (tot & 1) ? g[n - 1] : -g[n - 1]; Add(ans, tmp); } printf("%I64d\n", (ans + MOD) % MOD); return 0; } #undef Add再贴一个朴素的状压代码:
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <map> using std::map; using std::make_pair; const char fi[] = "domino.in"; const char fo[] = "domino_simple.out"; const int maxN = 20, MOD = 19901013; map <int, int> pst, ths; map <int, int>::iterator iter; char mp[maxN][maxN]; int n, m; inline void Add(int status, int &val) { if (ths.find(status) == ths.end()) {ths.insert(make_pair(status, val)); return;} int &tmp = ths[status]; if ((tmp += val) >= MOD) tmp -= MOD; return; } int main() { freopen(fi, "r", stdin); freopen(fo, "w", stdout); scanf("%d%d\n", &n, &m); for (int i = 0; i < n; ++i) gets(mp[i]); if (n < 3 || m < 3) {printf("0\n"); return 0;} for (int i = 0; i < n - 1; ++i) { bool flag = 1; for (int j = 0; j < m; ++j) flag &= (mp[i][j] == 'x' || mp[i + 1][j] == 'x'); if (flag) {printf("0\n"); return 0;} } for (int j = 0; j < m - 1; ++j) { bool flag = 1; for (int i = 0; i < n; ++i) flag &= (mp[i][j] == 'x' || mp[i][j + 1] == 'x'); if (flag) {printf("0\n"); return 0;} } ths.insert(make_pair(0, 1)); for (int i = 0; i < n; ++i) for (int j = 0; j < m; ++j) { std::swap(pst, ths); ths.clear(); int x = m - j - 1; for (iter = pst.begin(); iter != pst.end(); ++iter) { int val = iter -> second, Last = iter -> first; if (!val) continue; if (!j) { if (Last & 1) continue; else { int tmp = Last & (((1 << m - 1) - 1) << m + 1); ((Last &= (1 << m + 1) - 1) >>= 1) |= tmp; } if (i && !(Last & ((1 << m) - 1))) continue; } int wx = (Last >> x) & 3, Now = Last - (wx << x); if (mp[i][j] == 'x') {if (!wx) Add(Now, val); continue;} if (wx < 3) Add(Now, val); if (!wx) { if (j < m - 1 && mp[i][j + 1] - 'x') Add(Now | (1 << x) | (1 << x + m), val); if (i < n - 1 && mp[i + 1][j] - 'x') Add(Now | (2 << x), val); } } } if (ths.find(((1 << m - 1) - 1) << m + 1) == ths.end()) printf("0\n"); else printf("%d\n", ths[((1 << m - 1) - 1) << m + 1]); return 0; }