【刷题日记】经典问题:互不侵犯(状压DP)
P1896 [SCOI2005] 互不侵犯
题目链接
题目描述
在 \(N \times N\) 的棋盘里面放 \(K\) 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 \(8\) 个格子。
输入格式
只有一行,包含两个数 \(N,K\)。
输出格式
所得的方案数
输入输出样例 #1
输入 #1
3 2
输出 #1
16
说明/提示
数据范围及约定
对于全部数据,\(1 \le N \le 9\),\(0 \le K \le N\times N\)。
题解
思路
这道题需要用状压DP。状压,指状态压缩,一般是指把某个复杂的状态压缩成一个数。
这道题中,每一行最多9个格子,每个格子有/无国王可以表示为1/0,则每行的状态可以压缩成一个二进制数,最大\(2^{10}-1 = 1023\).
只要上一行有1的位置,在这一行,左中右都没有1,那么就可以进行状态转移。
实现时,可以先预处理好每一个状态合不合法、有多少1,再逐行进行状态转移。
实现
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
bool il[2000];	//表示第i个状态是否合法(同行内不相互攻击)
i64 cnt[2000];	//表示第i个状态有多少国王
i64 dp[10][2000][100];	//表示第i行第j个状态下共放下num个国王的方案数
i64 n, k;
int main() {
	cin >> n >> k;
	i64 maxn = (1 << n) - 1;	//最大状态
	for (i64 i = 0; i <= maxn; i++) {
		il[i] = !(i & (i >> 1));	//判断是否合法
		cnt[i] = cnt[i >> 1] + (i & 1);	//递推地计数
	}
	//初始化第一行
	for (i64 i = 0; i < n; i++)
		for (i64 j = 0; j <= maxn; j++)
			dp[0][j][cnt[j]] = il[j];
	//状压DP
	for (i64 i = 1; i < n; i++)
		for (i64 j = 0; j <= maxn; j++)
			for (i64 lst_j = 0; lst_j <= maxn; lst_j++) {
				if (!il[j] || !il[lst_j] || (lst_j & j) || (lst_j & (j >> 1)) || (lst_j & (j << 1)))
					continue;
				for (i64 num = cnt[lst_j]; num + cnt[j] <= k; num++)
					dp[i][j][num + cnt[j]] += dp[i - 1][lst_j][num];
			}
	//统计ans
	i64 ans = 0;
	for (i64 i = 0; i <= maxn; i++)
		ans += dp[n - 1][i][k];
	cout << ans;
	return 0;
}

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号