Time Limit:1000MS Memory Limit:65536K
Total Submit:95 Accepted:38
Description
在
n
∗
n
(
n
≤
20
)
n*n(n≤20)
n∗n(n≤20)的方格棋盘上放置
n
n
n个车(可以攻击所在行、列),有些格子不能放,求使它们不能互相攻击的方案总数。
Input
第一行为棋盘的大小
n
n
n
第二行为障碍的数量
m
m
m
第三行到第
m
+
3
m+3
m+3为
m
m
m个障碍
Output
总数
Sample Input
4
2
1 1
2 2
Sample Output
14
解题思路
状态压缩递推(States Compressing Recursion,SCR)
我们一行一行放置。
取棋子的放置情况作为状态,某一列如果已经放置棋子则为
1
1
1,否则为
0
0
0。这样,一个状态就可以用一个最多
20
20
20位的二进制数表示。
例如
n
=
5
n=5
n=5,第
1
、
3
、
4
1、3、4
1、3、4列已经放置,则这个状态可以表示为
01101
01101
01101(从右到左)。设
f
[
s
]
f[s]
f[s]为达到状态
s
s
s的方案数,则可以尝试建立
f
f
f的递推关系。
考虑
n
=
5
,
s
=
01101
n=5,s=01101
n=5,s=01101,
因为我们是一行一行放置的,所以当达到
s
s
s时已经放到了第三行。又因为一行能且仅能放置一个车,所以我们知道状态
s
s
s一定来自(没障碍物的情况下):
1. 前两行在第3、4列放置了棋子(不考虑顺序,下同),第三行在第1列放置;
2. 前两行在第1、4列放置了棋子,第三行在第3列放置;
3. 前两行在第1、3列放置了棋子,第三行在第4列放置。
图示:
在枚举s中的1的时候判断一下是否有障碍物即可
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn=1<<20;
int n,m,num,x,y;
long long vis[25],f[maxn];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
vis[x]+=1<<(y-1);
}
f[0]=1;
for(int i=1;i<1<<(n);i++)//枚举可能出现的状态
{
int num=0;
for(int j=i;j;j-=j&(-j)) num++;//计算当前枚举的状态是哪一行
for(int j=i;j;j-=j&(-j))
if(!(vis[num]&(j&(-j))))//判断该位置是否可以放置(j&(-j)为这一行你要放的位置,vis[num]为这一行你不能放的位置)
f[i]+=f[i&~(j&(-j))];
}
printf("%lld",f[(1<<n)-1]);
}