您的位置:首页 > 大数据 > 人工智能

★【动态规划】【状态压缩】【容斥原理】【ZJOI2009】多米诺骨牌

2012-04-10 16:36 369 查看
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ini iterator output pair input c