洛谷P1896 [SCOI2005]互不侵犯解题报告

互不侵犯

题目链接


emmm

题干

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

输入样例

3 2

输出样例

16

数据范围

1 ≤ N ≤ 9 , 0 ≤ K ≤ N 2 1 \leq N \leq 9, 0 \leq K \leq N^2 1N9,0KN2



解法一、

知识点

  1. 位运算
  2. dp
    3.状压dp=(位运算+dp)

解法概括

首先我们考虑只有一列的情况

我们可以设 f ( i , j , k ) f(i,j,k) f(i,j,k)

  • i i i:第 i i i
  • j j j:0/1,表示这一行是否有国王
  • k k k:已经放置的国王数

那么显然我们的状态转移方程为
f [ i ] [ 1 ] [ k ] = f [ i − 1 ] [ 0 ] [ k ] f[i][1][k]=f[i-1][0][k] f[i][1][k]=f[i1][0][k]
f [ i ] [ 0 ] [ k ] = f [ i − 1 ] [ 1 ] [ k ] f[i][0][k]=f[i-1][1][k] f[i][0][k]=f[i1][1][k]
边界条件为 f [ 1 ] [ 1 ] [ 1 ] = f [ 1 ] [ 0 ] [ 0 ] = 1 f[1][1][1]=f[1][0][0]=1 f[1][1][1]=f[1][0][0]=1
最后统计 a n s = f [ n ] [ 1 ] [ K ] + f [ n ] [ 0 ] [ K ] = 2 ( → _ → ) ans=f[n][1][K]+f[n][0][K]=2 (→\_→) ans=f[n][1][K]+f[n][0][K]=2(_)

其次我们再考虑两列的情况

我们需要设 f ( i , j 1 , j 2 , k ) f(i,j_1,j_2,k) f(i,j1,j2,k)

状态转移方程这里就不在累述,显而易见,我们的状态表示数组变成了四维的,推而广之

  • 三列: f ( i , j 1 , j 2 , j 3 , k ) f(i,j_1,j_2,j_3,k) f(i,j1,j2,j3,k)
  • 四列: f ( i , j 1 , j 2 , j 3 , j 4 , k ) f(i,j_1,j_2,j_3,j_4,k) f(i,j1,j2,j3,j4,k)
  • n列: f ( i , j 1 , j 2 , j 3 , ⋯   , j n , k ) f(i,j_1,j_2,j_3 ,\cdots,j_n,k) f(i,j1,j2,j3,,jn,k)

有几列,需要n+2维的数组来维护。

然而最多可以有9列,所以,如果按照一般的做法,应该开一个十二维的数组来维护。也就是需要
9 × 9 9 × 81 = 282429536481 ∗ 4 字 节 ≈ 1 T B 9\times9^9\times81=282429536481*4字节\approx1TB 9×99×81=28242953648141TB的内存。

所以我们需要一个更加优秀的方法来维护。

通过观察,我们发现对于 f ( i , j 1 , j 2 , j 3 , ⋯   , j n , k ) f(i,j_1,j_2,j_3,\cdots,j_n,k) f(i,j1,j2,j3,,jn,k)来说, j 1 , j 2 , j 3 , ⋯   , j n j_1,j_2,j_3,\cdots,j_n j1,j2,j3,,jn都是1或0,所以,我们可以很自然地想到二进制。

于是,正解就这么被想到了:用一个二进制数来表示 j 1 , j 2 , j 3 , ⋯   , j n j_1,j_2,j_3,\cdots,j_n j1,j2,j3,,jn,于是,一个最多十二维的数组,被我们压缩成了一个三维的数组
f ( i , j , k ) f(i,j,k) f(i,j,k)

  • i i i:第i行
  • j j j:该行国王放置的状态
  • k k k:前i行有k个国王

但是到这里我们去dp还是不方便,所以,我们需要先dfs预处理出所有的可能,然后再去dp

  • s i t [ ] sit[] sit[]:该状态下这一行国王的总数
  • g s [ ] gs[] gs[]:这个状态(国王放置的状态)
void dfs(int tot,int sum,int tag)
{
	if(tag>=n)//找完了
	{
		sit[++cnt]=tot;
		gs[cnt]=sum;
		return ;//这里需要结束
	}
	dfs(tot,sum,tag+1);//因为这里不用第tag个,所以tag+1
	dfs(tot+(1<<tag),sum+1,tag+2);//这里要用第tag个,所以需要tag+2
}

那么我们的状态转移方程就是
f [ i ] [ j ] [ s ] + = f [ i − 1 ] [ k ] [ s − g s [ j ] ] f[i][j][s]+=f[i-1][k][s-gs[j]] f[i][j][s]+=f[i1][k][sgs[j]]

  • i i i:第i行
  • j j j:当前的状态
  • k k k:上一行的状态
  • s s s:枚举的国王数

最后统计
a n s + = f [ N ] [ i ] [ K ] ans+=f[N][i][K] ans+=f[N][i][K]

代码实现

#include
#define ll long long

using namespace std;

ll f[10][2000][520];

inline ll read()
{
	ll f=1,k=0;
	char c=getchar();
	
	while(c>'9'||c<'0')
	{
		if(c=='-')f=-1;
		c=getchar();
	}
	
	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
	
	return f*k;
}

ll n,m,cnt;
ll sit[2333],gs[2333];

void dfs(int tot,int sum,int tag)
{
	if(tag>=n)
	{
		sit[++cnt]=tot;
		gs[cnt]=sum;
		return ;
	}
	dfs(tot,sum,tag+1);
	dfs(tot+(1<<tag),sum+1,tag+2);
}


int main()
{
	n=read();m=read();
	
	dfs(0,0,0);
	
	for(int i=1;i<=cnt;i++)f[1][i][gs[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]&(sit[k]<<1))continue;
				
				if((sit[j]<<1)&sit[k])continue;
				
				for(int s=m;s>=gs[j];s--)f[i][j][s]+=f[i-1][k][s-gs[j]];
			}
	
	ll ans=0;
	
	for(int i=1;i<=cnt;i++)ans+=f[n][i][m];
	
	printf("%lld",ans);
	
	return 0;
}

												❀完结撒花❀
											❀				❀
										❀						❀

你可能感兴趣的:(刷题)