P1896[SCOI2005]互不侵犯 解题笔记

由于答案可能会很大,不难想到使用状压dp解决。

考虑使用二进制来表示:

\[100010_{(2)} = 34_{(10)} \]

这种访问方式比数组寻址更加简单快速,如 \((1 << (k - 1)) \& s\) 可以询问状态 \(s\) 的第 \(k\) 位上是 \(1\) 还是 \(0\)\((j >> k) << k\) 可以把状态 \(j\) 的二进制表示下最右边几位清零,而数组不可以 \(O(1)\) 的时间复杂度清零。

考虑使用 \(f_{i, j, k}\) 表示前 \(i\) 行,第 \(i\) 行状态的二进制表示(十进制下的值)为 \(j\) ,放置了 \(k\) 个国王的方案数,那么状态转移方程:

\(f_{i, j, k} = \sum f_{i - 1, new, k - getlen(j)}\)

其中 \(new\) 表示所枚举的上一行的所有合法状态,\(getlen(j)\) 表示 \(j\) 状态有多少位为 \(1\)

code:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[20][205][1100], ans, n, K, m, nn, L;
int _get(int x) {
	int sum = 0;
	while(x) {
		if(x & 1) sum ++;
		x >>= 1;
	}
	return sum;
}
bool check(int x, int y) {
	if(x & y) return 1;
	if((x << 1) & y) return 1;
	if((x >> 1) & y) return 1;
	if((y >> 1) & y) return 1;
	return 0;
}
signed main() {
	cin >> n >> K;
	dp[0][0][0] = 1;
	nn = (1 << n) - 1;
	for(int i = 1;i <= n;i ++) {
		for(int j = 0;j <= K;j ++) {
			for(int k = 0;k <= nn;k ++) {
				L = _get(k);
				if(L > j) continue;
				if(k & (k >> 1)) continue;
				for(int l = 0;l <= nn;l ++) {
					if(check(k, l)) continue;
					dp[i][j][k] += dp[i - 1][j - L][l];
				}
			}
		}
	}
	for(int i = 0;i <= nn;i ++) {
		ans += dp[n][K][i];
	} 
	cout << ans;
} 
posted @ 2025-10-19 20:12  FurinaQWQ  阅读(7)  评论(0)    收藏  举报