状压dp入门题 《P1896 [SCOI2005]互不侵犯》

P1896 [SCOI2005]互不侵犯

原题戳我qwq

题目描述

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

输入格式

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

输出格式

所得方案数

样例:
输出:
3 2
输入:
16

状态压缩动态规划 俗称 状 压 d p 状压dp dp
状态压缩通常采用二进制来表示状态和运行,
十进制的方式储存数据。

================================================
比如:(0代表土地贫瘠,1代表土地肥沃)
13 ( 10 ) → → → 1101 ( 2 ) 13(10) →→→ 1101(2) 131011012
我们可以得知哪些区域属于贫瘠土地或肥沃土地。
这便是状态压缩。

================================================

讲到二进制,就不得不讲一下位运算了,
因为一般涉及到状压dp,位运算是绝对少不了的。
关于位运算,大家可以去这个博客看一看↓
戳我戳我

下面正式进入正题 !!!
先定义变量:

long long dp[10][15000][80]; //dp[i][j][k]表示第i行,状态为j,前面摆了k个国王时的方案数; 
long long state[777777] , king[77777] ;//state[]是当前状态,king[]是当前行的国王数;
long long num;//状态总数
long long ans;//方案总数

预处理:

inline void inte() {
	int tot = (1<<N) - 1;//最多到这个时候,就是二进制下,每一位上都放上国王,当然有不空位行的,为了方便下文排除; 
	for (re int i = 0; i <= tot; i++) {
		if (!( (i<<1) & i)) {//因为要互不侵犯,所以,两个国王之间必须隔一个,这是判断是否满足题意国王之间不相互攻击; 
			state[ ++num ] = i;//找到了满足的,记录这个状态; 
			int t = i;
			while (t) {//判断这个状态有多少个国王,也就是t在二进制下有多少个1; 
			    king[ num ] += t % 2,//如果有余数,证明这里有一个国王 
				t >>= 1; //和t/=2一样,但是快一点 
		    }
		}
	}
	return;
}

代码注释讲的挺详细的,我直接上了
C o d e : Code: Code:

#include<bits/stdc++.h>
#define ll long long
#define re register
using namespace std;
ll N, K, num, ans;//num是用来记录状态总数的,ans是用来计算一共有多少种方案的;
ll f[15][15005][105];//dp[i][j][k]表示第i行,状态为j,前面摆了k个国王时的方案数; 
ll state[777777], king[777777];//state[]是当前状态,king[]是当前行的国王数;
inline void inte() {
	int tot = (1<<N) - 1;//最多到这个时候,就是二进制下,每一位上都放上国王,当然有不空位行的,为了方便下文排除; 
	for (re int i = 0; i <= tot; i++) {
		if (!( (i<<1) & i)) {//因为要互不侵犯,所以,两个国王之间必须隔一个,这是判断是否满足题意国王之间不相互攻击; 
			state[ ++num ] = i;//找到了满足的,记录这个状态; 
			int t = i;
			while (t) {//判断这个状态有多少个国王,也就是t在二进制下有多少个1; 
			    king[ num ] += t % 2,//如果有余数,证明这里有一个国王 
				t >>= 1; //和t/=2一样,但是快一点 
		    }
		}
	}
	return;
}
int main() {
	scanf ("%lld%lld",&N, &K);
	inte();
	for (re int i = 1; i <= num; i++) {//预处理第一行 
		if (king[i] <= K){//国王放置数量肯定不能超过总数 
			f[1][i][king[i]] = 1; 
		}
	}
	for (re int i = 2; i <= N; i++) {
	    for (re int j = 1; j <= num; j++) {
	     	for (re int k = 1; k <= num; k++) {
			    if (state[j] & state[k]) continue; //上下不允许相邻 
			    if (state[j] & (state[k] << 1)) continue; //右上角不允许相邻 
				if ((state[j] << 1) & state[k]) continue; //左上角也不允许 
			    for (re int l = 1; l <= K; l++) {//s表示本行以上用了多少国王;  
			    	if (king[j] + l > K) continue;//如果超过了国王总数,那么不成立 
			    	f[i][j][king[j]+l] += f[i-1][k][l];//还记得dp[i][j][k]中的k表示已经用过的国王数,而king[]是本行的,s是本行以前的; 
	     		}
			 }
	    }
	}
	for (re int i = 1; i <= N; i++)
	    for (re int j = 1; j <= num; j++)
	        ans += f[i][j][K];
	printf ("%lld\n", ans);
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值