Background
有一个n*m的棋盘(n、m≤80,n*m≤80)要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻(每个棋子最多和周围4个棋子相邻)。求合法的方案总数。
Input
本题有多组测试数据,每组输入包含三个正整数n,m和k。
Output
对于每组输入,输出只有一个正整数,即合法的方案数。
Sample Input
2 2 3
4 4 1
Sample Output
0
16
solution
这道题一看状态非常多,就一定是状压。
我们很容易就能想到有以下几个状态:
- 每一行放了多少个旗子;
- 已经用了多少个旗子;
- 已经放的这些旗子能不能保证合法,即上下左右均不相邻。
我们先来考虑只有一行的情况,即转化为要求在这一行里边填充k个旗子,要求任意两个都不相邻,这个时候的dp应该怎么表示?这就很简单了,直接就是
dp[i][j][x]
,代表已经到了第i列,已经使用了j个旗子,而且当前第i列的状态就是x(当然这里x只能是0和1,这里0代表这个第i列没有放旗子,1就代表这个位置放了旗子)的总方案数,递推关系是怎么写?其实也很简单,
dp[i][j][0]=dp[i−1][j][0]+dp[i−1][j][1];
dp[i][j][1]=dp[i−1][j−1][0];
//这里只能是dp[i-1][j-1][0],因为第i列已经放了,那么第i-1列就一定不能放。
当然这里你需要考虑到二维的局面,怎么考虑,把行对应于列,每一列的状态转化为每一行的状态,前i列使用了j个旗子变成前i行使用了j个旗子就这样思考。
综上考虑,我们会想到要有一个这样的dp,就是
dp[i][j][x]
,这里代表的是:
填充旗子已经填到第i行了,已经使用了j个旗子,而且当前第i行的状态就是x的这么一个
表示前i行的总方案数。
那么递推:
dp[i][j][x]+=dp[i−1][j−num(mark[x])][y]
;
这里的x是当前第i行的状态,而这个
mark[x]
代表当前状态下的十进制表示,也就是说把一个状态表示成十进制之后就是
mark[x]
了,这里为什么是
j−num(mark[x])
呢?因为啊,你这样想。反过来推。如果你在前
i−1
行已经使用了
j−num(mark[x])
个旗子,而且
num(mark[x])
就代表第i行你使用的旗子,那么你在前i行是不是就使用了j个旗子
就这样逆推。这个
mark[x]
和
mark[y]
分那别代表第i行和第i-1行的状态的十进制表示。
最后你只要把
dp[n][k][i]
其中i是从0到最多状态的那些状态,把他们加起来。
code
#include <cstdio>
#include <cstring>
#include <algorithm>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;
LL dp[85][25][1<<6];
LL mark[1<<6],zt,ans;
int n,m,k;
inline int swap(int &a,int &b) {
a^=b;b^=a;a^=b;
}
inline int num(LL x) {
int ret=0;
while(x){
if(x&1)ret++;
x>>=1;
}
return ret;
}
bool pd(int x) {
return (!(x&(x<<1)));
}
int main() {
while(~scanf("%d%d%d",&n,&m,&k)) {
zt=0;
memset(dp,0,sizeof dp);memset(mark,0,sizeof mark);
if(n<m) swap(n,m);
for(LL i=0;i<(1<<m);i++) {
if(pd(i)) {
dp[1][num(i)][zt]=1;
mark[zt++]=i;
}
}
for(int i=2;i<=n;i++)
for(int j=0;j<=k;j++)
for(LL x=0;x<zt;x++)
for(LL y=0;y<zt;y++) {
if(((mark[x]&mark[y])==0)&&j>=num(mark[x])) {
dp[i][j][x]+=dp[i-1][j-num(mark[x])][y];
}
}
ans=0;
for(LL i=0;i<zt;i++)
ans+=dp[n][k][i];
printf("%lld\n",ans);
}
return 0;
}