题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1087
题解:
刚开始光想着爆搜、打表,观摩大犇们的博客,才薛习了正解:状态压缩DP
对于每一格,只有放国王或者不放国王两种可能,所以一行的状态可以表示为一个01串,可以想到压缩成一个十进制数存储,计算出所有可能的状态并编号,然后将互相矛盾的状态标记出来
之后开始DP,f[i][j][k]表示第i行,第j种状态,放k个棋子的状态数
四层循环,枚举行数i、状态数j、放的国王的个数l、上一行的状态m
当满足两行的状态不矛盾,并且两种状态的国王数不超过l时,状态转移方程:
f[i][j][l]+=f[i-1][m][l-j状态的国王数]
最后,将f[n][1...状态总数][k]加起来得到结果
1 #include<cstdio> 2 #define MAXN 100 3 int n,k,num_of_status,status[MAXN],status_sum[MAXN];//num_of_status记录状态总数,status记录十进制压缩后的状态,status_sum记录某种状态放了几个国王 4 long long ans,f[MAXN]/*行号*/[MAXN]/*状态编号*/[MAXN*10]/*放的国王数*/; 5 bool status_can[MAXN][MAXN];//某两种状态是否矛盾 6 void dfs(int x,int y,int z)//计算一行中所有状态,表示这一行放x个棋子,用了y个格子,z表示十进制压缩后的状态 7 { 8 status[++num_of_status]=z; 9 status_sum[num_of_status]=x; 10 if(x>=(n+1)/2||x>=k)return;//已经没有足够的空间放国王或者国王已经放完了 11 for(int i=y+2;i<=n;i++)dfs(x+1,i,z+(1<<(i-1))); 12 } 13 void get_can() 14 { 15 for(int i=1;i<=num_of_status;i++) 16 { 17 for(int j=1;j<=num_of_status;j++) 18 { 19 if(!((status[i]&status[j])||((status[i]>>1)&status[j])||(status[i]&(status[j]>>1))))status_can[i][j]=status_can[j][i]=true;//注意左下和右下也不能放国王,所以还要将这一行左移一位或者右移一位判断 20 } 21 } 22 for(int i=1;i<=num_of_status;i++)f[1][i][status_sum[i]]=1;//初始化第一行的状态 23 } 24 int main() 25 { 26 scanf("%d%d",&n,&k); 27 dfs(0,-1,0); 28 get_can(); 29 for(int i=2;i<=n;i++) 30 { 31 for(int j=1;j<=num_of_status;j++) 32 { 33 for(int l=0;l<=k;l++) 34 { 35 if(status_sum[j]>l)continue; 36 for(int m=1;m<=num_of_status;m++) 37 { 38 if(status_can[m][j]&&status_sum[j]+status_sum[m]<=l) 39 { 40 f[i][j][l]+=f[i-1][m][l-status_sum[j]]; 41 } 42 } 43 } 44 } 45 } 46 for(int i=1;i<=num_of_status;i++)ans+=f[n][i][k]; 47 printf("%lld ",ans); 48 return 0; 49 }