Problem B. Domino Colorings
题意
n <= 6 , m <= 300 , 的网格,用长度为2的砖头(恰好1黑1白)。问能够铺出的不同颜色方案数
注意不同铺法但是颜色相同算一种
题解
对颜色记录插头以下from claris
若已经知道了每个格子的颜色,那么可以DP判断是否能由某种骨牌铺成,设dp[S]表示轮廓线上n个点匹配状态为S是否可行即可。
现在不知道每个格子的颜色,那么需要DP这些颜色,设f[i][j][c][v]表示考虑到(i,j),轮廓线上n个点颜色为c,dp[S]这个大小为2n的布尔数组取值为v时的方案数。
当n=6时状态数也只有不到2000个,所以可以通过。
我的理解
因为颜色相同的状态在任何时候轮廓线上的颜色和它所对应的可能插头一定相同。
转移的时候枚举填的颜色,更新每种插头的转移
trick
插头只用记录n个,不用记录n + 1个。因为当前的上插头可以记录在上方格子的右插头中。因为2^64刚好可以用一个unsigned long long存下,否则会麻烦很多
bitset需要自己写比较函数,再map就T了。
这里的插头相同于轮廓线的后一列的覆盖状态。也可以理解成正常的轮廓线DP中的记录状态。
有两种写法:
在第一格处填砖头,记录对后面的覆盖
在最后一格处填砖头,记录前面的砖头
我一般采用第一种
总结:
对于轮廓线DP的技巧,还不够熟。这种DP,只要想清楚了,是很好写的。但是比较难调试。
要保证记录的轮廓线的信息足够转移!
一开始我想直接记录后两列的轮廓线的颜色,但是这样不能保证与相同颜色的状态一一对应。必须保证所有插头(即转移)相同,才能确定两个状态一定相同。
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
#define fore(i,x)for (register int i = head[x] ; i ; i = e[i].next)
#define forup(i,l,r) for (register int i = l ; i <= r ; i += lowbit(i))
#define fordown(i,id) for (register int i = id ; i ; i -= lowbit(i))
#define pb push_back
#define prev prev_
#define stack stack_
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef bitset<128> bit;
typedef pair<int,ull> pr;
const ld inf = 2e18;
const int maxn = 100020;
const int mod = 1e9 + 7;
int n,m,now,last;
map <pr,int> f[2];
inline void up(int &x,int y){
x += y;
if ( x >= mod ) x -= mod;
}
void solve(){
if ( n * m & 1 ){ puts("0"); return; }
f[now][mp(0,1)] = 1;
rep(i,1,m){
rep(j,1,n){
last = now , now ^= 1 , f[now].clear();
for (auto it : f[last]){
int col = it.fi.fi;
ull S = it.fi.se;
int num = it.se;
int uc = (col >> (j - 1)) & 1 , lc = (col >> j) & 1;
col -= lc << j;
rep(k,0,1){
ull next_S = 0; int cur_col = col | (k << j);
rep(x,0,(1 << n) - 1) if ( S & (1ull << x) ){
//左插头
if ( x & (1 << (j - 1)) ){
if ( lc ^ k ){
next_S |= 1ull << (x ^ (1 << (j - 1)));
}
continue;
}
//新建插头
next_S |= 1ull << (x ^ (1 << (j - 1)));
//上插头: 直接记录在上方的左插头处,可以看做把左插头转向成下插头
if ( j > 1 && uc != k && (x & (1 << (j - 2))) ) next_S |= 1ull << (x ^ (1 << (j - 2)));
}
pr cur = mp(cur_col,next_S);
if ( next_S ) up(f[now][cur],num);
}
}
}
}
int ans = 0;
for (auto it : f[now]){
if ( it.fi.se & 1 ) up(ans,it.se);
}
cout<<ans<<endl;
}
int main(){
scanf("%d %d",&n,&m);
solve();
}