P1896 [SCOI2005] 互不侵犯题解__状压DP

题面

题目描述

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

输入格式

只有一行,包含两个数 N , K N,K N,K

输出格式

所得的方案数

样例 #1

样例输入 #1

3 2

样例输出 #1

16

提示

数据范围及约定

对于全部数据, 1 ≤ N ≤ 9 1 \le N \le 9 1N9 0 ≤ K ≤ N × N 0 \le K \le N\times N 0KN×N


题解

思路&分析

这题 N N N K K K的范围非常小, 而且国王只有在/不在这一格两种状态, 可以考虑状压DP
我们定义一个状态
d p [ i ] [ j ] [ S ] dp[i][j][S] dp[i][j][S]表示当前模拟到了第 i i i行, 一共用了 j j j个王, 当前行的状态为 S S S(一个01串, 被压成10进制, 1表示该格放了王, 0表示没有 e g :    10 1 ( 2 进制 ) = = 5 ( 10 进制 ) eg: \;101_{(2进制)}==5_{(10进制)} eg:101(2进制)==5(10进制)表示1号格和3号格放了王, 2号格没有)

现在考虑状态转移
d p [ i ] [ j ] [ S i ] + = d p [ i − 1 ] [ j − n u m S i ] [ S i − 1 ] dp[i][j][S_i]+=dp[i-1][j-num_{S_i}][S_{i-1}] dp[i][j][Si]+=dp[i1][jnumSi][Si1] n u m S num_S numS表示 S S S中有多少个1(多少个王)

由题意可得, 一个王周围 3 × 3 3\times3 3×3范围内不允许存在王

所以 S S S对于自身, 应满足
S    &    S < < 1         ∣          S    &    S > > 1      =      0 ( 请自行体会 ) S\;\&\;S<<1\;\:\;\;|\;\;\;\;S\;\&\;S>>1\;\;=\;\;0_{(请自行体会)} S&S<<1S&S>>1=0(请自行体会)同理 亦得, 对于上下两行的 S i S_i Si S i − 1 S_{i-1} Si1, 也应当满足
( S i    ∣    S i > > 1    ∣    S i < < 1 )    &    S i − 1      =      0 ( 也请自行体会 ) (S_i\;|\;S_i>>1\;|\;S_i<<1)\;\&\;S_{i-1}\;\;=\;\;0_{(也请自行体会)} (SiSi>>1Si<<1)&Si1=0(也请自行体会)所以, 我们只需要枚举 i ,    j ,    S i i,\;j,\;S_i i,j,Si S i − 1 S_{i-1} Si1即可
时间复杂度: O ( n k 2 2 n ) O(nk2^{2n}) O(nk22n)

优化

我们发现, 枚举所有 S S S的过程是由很多重复的, 每次都要判断其合法性
所以我们可以预处理出合法的状态 S S S, 把它们记录下来, 每次调用它们即可

void init(){
	for(int x=0;x<(1<<n);x++){
		if(!((x&(x<<1))||(x&(x>>1)))){
			cnt++;
			S[cnt]=x;
			int xx=x,ans=0;
			while(xx!=0){
				if(xx%2==1){
					num[x]++;
				}
				xx>>=1;
			}
		}
	}
}

其中, 当 n = 9 n=9 n=9时, 只有 341 341 341种合法状况
时间复杂度就降成了 O ( 34 1 2 n k ) ( m a x n = 84 , 768 , 849 ≤ 3 × 1 0 8 ) O(341^2nk)_{(maxn=84,768,849\le3\times10^8)} O(3412nk)(maxn=84,768,8493×108)
至此就结束哩

AC Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define YES "YES"


int n,k;
int num[1<<10],S[1<<10],cnt=0;
int dp[10][82][1<<9];

void init(){
	for(int x=0;x<(1<<n);x++){
		if(!((x&(x<<1))||(x&(x>>1)))){
			cnt++;
			S[cnt]=x;
			int xx=x,ans=0;
			while(xx!=0){
				if(xx%2==1){
					num[x]++;
				}
				xx>>=1;
			}
		}
	}
}

void find(int x){
	int a[10];
	int ans=0;
	while(x!=0){
		if(x&1){
			a[++ans]=1;
		}else{
			a[++ans]=0;
		}
		x>>=1;
	}
	for(int x=10;x>ans;x--){
		cout << 0;
	}
	for(int x=ans;x>=1;x--){
		cout << a[x];
	}
//	cout << endl;
}


signed main(){
	cin >> n >> k;
	if(k>25){
		cout << 0;
		return 0;
	}
	init();
//	for(int x=1;x<=cnt;x++){
//		cout << x << ':' << num[S[x]] << ' ' << S[x] << ' ';
//		find(S[x]);
//	}
	dp[0][0][0]=1;
	for(int x=1;x<=n;x++){
		for(int y=0;y<=k;y++){
			for(int z=1;z<=cnt;z++){
				int now1=S[z];
				int ans1=num[now1];
				if(ans1>y){
					continue;
				}
				for(int i=1;i<=cnt;i++){
					int now2=S[i];
					int ans2=num[now2];
					if(!(   (  now1 | ((now1<<1) | (now1>>1))  )   &   (now2)   )){
						dp[x][y][now1]+=dp[x-1][y-ans1][now2];
					}
				}
			}
		}
	}
	int ans=0;
	for(int x=1;x<=cnt;x++){
		ans+=dp[n][k][S[x]];
	}
	cout << ans;
	return 0;
}
  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值