Time Limit:1000MS Memory Limit:65536K
Total Submit:74 Accepted:40
Description
有一个nm的棋盘(n、m≤80,nm≤80)要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻。求合法的方案总数。
Input
n,m,k
Output
方案总数
Sample Input
3 3 2
Sample Output
24
解题思路
观察题目给出的规模,
n
、
m
≤
80
n、m≤80
n、m≤80,这个规模要想用状态压缩是困难的,280无论在时间还是空间上都无法承受
然而我们还看到
n
∗
m
≤
80
n*m≤80
n∗m≤80
稍微思考我们可以发现:
9
∗
9
=
81
>
80
9*9=81>80
9∗9=81>80,即如果
n
,
m
n,m
n,m都大于等于9,将不再满足
n
∗
m
≤
80
n*m≤80
n∗m≤80这一条件。所以,我们有n或m小于等于8,而
2
8
2^8
28是可以承受的!
我们假设
m
≤
n
,
n
m≤n,n
m≤n,n是行数
m
m
m是列数,则每行的状态可以用m位的二进制数表示
但是本题和例1又有不同:例1每行每列都只能放置一个棋子,而本题却只限制每行每列的棋子不相邻
上例中枚举当前行的放置方案的做法依然可行。我们用数组s保存一行中所有的
n
u
m
num
num的放置方案,则s数组可以在预处理过程中用
D
F
S
DFS
DFS求出,同时用
c
i
ci
ci保存第i个状态中1的个数以避免重复计算…
开始设计状态:
本题状态的维数需要增加,原因在于并不是每一行只放一个棋子,也不是每一行都要求有棋子,原先的表示方法已经无法完整表达一个状态。
我们用
f
i
,
j
,
k
fi,j,k
fi,j,k表示第i行的状态为sj且前i行总共放置k个棋子的方案数。沿用枚举当前行方案的做法,只要当前行的放置方案和上一行的不冲突。微观地讲,就是要两行的状态s1和s2中没有同为1的位即可,亦即
s
1
s1
s1&
s
2
=
0
s2=0
s2=0
然而,虽然我们枚举了第i行的放置方案,但却不知道其上一行(第i-1行)的方案 为了解决这个问题,我们不得不连第i-1行的状态一起枚举,则可以写出递推式:
其中s1=0,即在当前行不放置棋子; j和p(代码中的t)是需要枚举的两个状态编号。
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,k,num,bsy;
int f[100][1<<10][30],sum[1<<11],c[1<<11];
void dfs(int ans,int dep,int flag)
{
if(dep>n)
{
sum[++num]=ans;
c[num]=flag;
return;
}
dfs(ans,dep+1,flag);
dfs(ans+(1<<dep-1),dep+2,flag+1);
}
int main(){
scanf("%d%d%d",&n,&m,&k);
if(n>m) swap(n,m);
dfs(0,1,0);
for(int i=1;i<=num;i++)
f[1][sum[i]][c[i]]=1;
for(int i=2;i<=m;i++)
{
for(int j=1;j<=num;j++)
for(int t=1;t<=num;t++)
{
if(!(sum[j]&sum[t]))
{
for(int p=0;p<=k;p++)
if(p>=c[j])
f[i][sum[j]][p]+=f[i-1][sum[t]][p-c[j]];
}
}
}
for(int i=1;i<=num;i++)
bsy+=f[m][sum[i]][k];
printf("%d",bsy);
}