BZOJ1087: [SCOI2005]互不侵犯King 状压DP

时间:2022-12-17 17:57:39

1087: [SCOI2005]互不侵犯King

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 3014   Solved: 1767

Description

  在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上
左下右上右下八个方向上附近的各一个格子,共8个格子。

Input

  只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

Output

  方案数。

Sample Input

3 2

Sample Output

16

题解:

作为一个第一次写状压DP的蒟蒻,直接学习hzwer学长的代码,有很多很巧妙的状压DP技巧,值得学习(我就是给他打标记的而已)

很显然状态是f[i][j][ status],表示的是前i行,用了j个国王时,第i行的状态
对于状态,其中不可能有相邻的两个1,比如1101,1011都是不合法的状态,所以我们将所有合法的状态预处理来
其次,因为每个状态都是从上一行转移过来的,所以我们也需要判断任意两个状态之间会不会冲突(因为左上,右上,正上,这三个方向是不能同时有子的),这样剪一剪枝之后时间复杂度就很理想了
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long 
int n,m,S,cnt[1<<9];
ll f[10][100][1<<9];
bool c1[1<<9],c2[1<<9][1<<9];
ll ans;
void pre()
{
	int s;
	for(int i=0;i<=S;i++)
	if((i&(i>>1))==0)//判断有没有相邻的方式
	{
		s=0;
		for(int x=i;x;x>>=1)s+=(x&1);
		cnt[i]=s;//求每个状态1的个数 
		c1[i]=1;//为合法的排列 
	}
	for(int i=0;i<=S;i++)
	if(c1[i])
	for(int j=0;j<=S;j++)
	if(c1[j])
	if(((i&j)==0)&&((i&(j>>1))==0)&&((j&(i>>1))==0))
	c2[i][j]=1;//i,j为两种合法的可以在 
}
int main()
{
    scanf("%d%d",&n,&m);
    S=(1<<n)-1;
    pre();
    int tot=0;
    for(int i=0;i<=S;i++)
	if(c1[i]) f[1][cnt[i]][i]=1;
    for(int j=1;j<n;j++)
    for(int k=0;k<=S;k++)//k是在枚举上一行的状态 
	if(c1[k])
    for(int i=0;i<=S;i++)//i是在枚举这一行的状态 
	if(c1[i])
    if(c2[k][i])
    for(int p=cnt[k];p+cnt[i]<=m;p++)
    f[j+1][p+cnt[i]][i]+=f[j][p][k];
    ll ans=0;
    for(int i=0;i<=S;i++) ans+=f[n][m][i];
    printf("%lld",ans);
}