f[i][st]表示前i行,第i行的放置状态为st的方案数。
例题:
1.Mondriaan’s Dream POJ - 2411
题意:给出两个数 n、m,现在有 12、21 两种类型的方块,要求将 n*m 的区域填满,问一共有多少种方案
思路:木块有两种放置方法,横放或竖放,我们按行dp,横为11,竖为01(竖)。
1.确定第一行的放置方式。
2.从第二行开始dp,两行一起check。
3.确定最后一行的放置方式一定是全1,输出f[h][tot]
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int h,w;
bool init(int s){
for(int j=0;j<w;){
if(s&(1<<j)){
if(j==w-1)return false;
if(s&(1<<(j+1)))j+=2;
else return false;
}
else j++;
}
return true;
}
long long f[12][1<<12];
bool check(int now,int pre){
for(int j=0;j<w;){
if(now&(1<<j)){
if(pre&(1<<j)){
if(j==w-1||!(now&(1<<(j+1)))||!(pre&(1<<(j+1))))return false;
else j+=2;
}
else j++;
}
else{
if(pre&(1<<j))j++;
else return false;
}
}
return true;
}
int main(){
while(~scanf("%d%d",&h,&w)){
if(h==0&&w==0)break;
if((h&1)&&(w&1)){
printf("0\n");
continue;
}
if(h<w)swap(h,w);
int tot=(1<<w)-1;
memset(f,0,sizeof(f));
for(int s=0;s<=tot;s++){
if(init(s)){
f[1][s]=1;
// cout<<s<<"s"<<endl;
}
}
for(int i=2;i<=h;i++){
for(int j=0;j<=tot;j++){
for(int k=0;k<=tot;k++){
if(check(j,k)){
f[i][j]+=f[i-1][k];
}
}
}
}
printf("%lld\n",f[h][tot]);
}
return 0;
}
2.BZOJ 1087: [SCOI2005]互不侵犯King
链接
本题不需要初始化第一行的状态,直接判断所有状态的1的个数,在dp判断中看这个状态是否合理,合理进行dp,本行状态由上一行推来。这里要多开一维保存放置了多少king,所以转移时要多加一层循环。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll ans,f[10][512][26];
int g[512];
int n,m;
int main(){
scanf("%d%d",&n,&m);
if(m>25||m>=n*n){
printf("0\n");
}
else{
int t=1<<n;
f[0][0][0]=1;
g[0]=0;
for(int i=1;i<t;i++){
g[i]=g[i>>1]+(i&1);
}
for(int i=1;i<=n;i++){
for(int j=0;j<t;j++){
if(g[j]<=m&&!(j&(j>>1))){
for(int k=0;k<t;k++){
if(g[k]<=m&&!(k&(k>>1))&&!(k&j)&&!(j&(k>>1))&&!(j&(k<<1))){
for(int l=g[j]+g[k];l<=m;l++){
f[i][j][l]+=f[i-1][k][l-g[j]];
}
}
}
}
}
}
ans=0;
for(int i=0;i<t;i++){
ans+=f[n][i][m];
}
printf("%d\n",ans);
}
return 0;
}