点击这里查看原题
当时校赛的时候没做出来,现在才知道状压DP可以按格点转移。
按格点转移的状压DP可以先参考POJ 2411,这个题不同的地方是按2k进制压位,因为矩形的短边不会超过6,所以最大状态数为8^6,即2^18。对于每一位,如果值小于k,表示在这一列连续出现了1次,如果大于等于k,则表示在这一列连续出现了两次,放置的时候既要考虑横行也要考虑纵行。
/*
User:Small
Language:C++
Problem No.:XDU 1203
*/
#include<bits/stdc++.h>
#define ll long long
#define inf 999999999
using namespace std;
const int mod=1e9+7;
int n,m,k,f[2][1<<18],b[10],p,q,ans;
void dfs(int pos,int s){
if(pos==m+1){
f[q][s]=1;
return;
}
for(int i=0;i<k;i++){
if(pos>=3&&(s/b[pos-3]%b[1])==i&&(s/b[pos-2]%b[1])==i) continue;
dfs(pos+1,s+i*b[pos-1]);
}
}
void solve(){
if(n<m) swap(n,m);
p=0,q=1;
b[0]=1;
for(int i=1;i<=m+1;i++) b[i]=b[i-1]*2*k;//压为2k进制的m位数
memset(f[q],0,sizeof(f[q]));
dfs(0,0);
for(int i=1;i<n;i++){
for(int j=0;j<m;j++){
swap(p,q);
memset(f[q],0,sizeof(f[q]));
for(int s=0;s<b[m];s++){
if(f[p][s]==0) continue;
int di=s/b[j]%b[1];
if(di<k){//di<k表示这个数在这一列连续出现1次,还可以再出现一次
if(j<2) f[q][s+k*b[j]]=(f[q][s+k*b[j]]+f[p][s])%mod;//如果是这一行的前两个数,那么不需要考虑横行
else if(((s/b[j-2]%k)!=di%k||(s/b[j-1]%k)!=di%k)) f[q][s+k*b[j]]=(f[q][s+k*b[j]]+f[p][s])%mod;//否则需要考虑放置后这一行是否合法
}
for(int t=0;t<k;t++){
if(t==di%k) continue;
if(j>=2&&(s/b[j-2]%k)==t&&(s/b[j-1]%k)==t) continue;//如果放置后这一行不合法,则不能放置
f[q][s-di*b[j]+t*b[j]]=(f[q][s-di*b[j]+t*b[j]]+f[p][s])%mod;//变成了另一种颜色
}
}
}
}
ans=0;
for(int i=0;i<b[m];i++) ans=(ans+f[q][i])%mod;
cout<<ans<<endl;
}
int main(){
freopen("data.in","r",stdin);//
ios::sync_with_stdio(false);
while(cin>>n>>m>>k) solve();
return 0;
}

本文介绍了一种使用状态压缩动态规划(状压DP)解决特定类型问题的方法,特别是当问题涉及二维矩阵且短边长度不大于6时,可以通过按格点转移的方式来减少状态数,实现高效求解。

被折叠的 条评论
为什么被折叠?



