【SCOI2005】互不侵犯 题解(状压DP)
前言:一道状压DP的入门题(可惜我是个DP蒟蒻QAQ)
------------------
题意简述:求在一个$n*n$的棋盘中放$k$个国王的方案数。注:当在一个格子中放入国王后,以此格为中心的九宫格的其他八个格子将不能放置国王。
数据范围:$1\leq n\leq 9$,$1\leq k\leq n*n$。
------------------------------
看到数据范围,不是$dfs$就是状压DP。这道题我们考虑状压DP。
状压DP就是把某个阶段转换成二进制记录下来,一般用于数据范围较小的题目,状压因此得名。
国王个数是一个限制条件,所以这是一个阶段。在状压DP中,我们一般考虑以行作为阶段。因为上一行的放置情况关系到这一行的放置情况,所以我们还要再用一维表示放置情况,这一维要用到状压。
所以我们设$f[i][j][k]$为在前$i$行中放入$k$个国王,且这一行的摆放情况为$j$的方案数。摆放情况可以用$dfs$预先处理。
考虑转移过程中的限制条件:
如果$ sit[j]$ 与 $sit[k]=1 $,则表示上下相邻的格子都摆放了国王。
如果$ (sit[j]<<1)$ 与 $sit[k]=1 $,则表示左上或右下摆放了国王。
如果$ sit[j]$ 与 $(sit[k]<<1)=1 $,则表示右上或左下的格子摆放了国王。
左右相邻的情况在$dfs$中即可排除。
转移方程:$f[i][j][s]+=f[i-1][k][s-num[j]]$。$num[j]$表示放置情况为$j$时国王的放置个数。
边界:$f[1][i][num[i]]=1$。
其实貌似可以用滚动数组优化来省掉第一维,但我懒得写了。
注意开$long long$。
代码:
#include<bits/stdc++.h> #define int long long using namespace std; int n,m,cnt=0,ans=0; int sit[2005],num[2005];//预处理放置情况 int f[10][2005][100]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } void dfs(int pos,int sum,int node) { if (node>=n) { sit[++cnt]=pos; num[cnt]=sum; return; } dfs(pos,sum,node+1);//在此格不放入国王 dfs(pos+(1<<node),sum+1,node+2);//在此格放入国王,此时要跳过相邻的格子。 } signed main() { n=read(),m=read(); dfs(0,0,0); for (int i=1;i<=cnt;i++) f[1][i][num[i]]=1;//边界 for (int i=2;i<=n;i++) for (int j=1;j<=cnt;j++) for (int k=1;k<=cnt;k++) { if (sit[j]&sit[k]) continue; if ((sit[j]<<1)&sit[k]) continue; if (sit[j]&(sit[k]<<1)) continue; for (int s=m;s>=num[j];s--) f[i][j][s]+=f[i-1][k][s-num[j]];//转移 } for (int i=1;i<=cnt;i++) ans+=f[n][i][m]; printf("%ld",ans); return 0; }