SCOI2005 互不侵犯(状压DP)
SCOI2005 互不侵犯(状压DP)
题目大意
在\(N×N\)的棋盘里面放\(K\)个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
Input
一行\(N,K\)
Output
方案数
Solution
状态压缩DP基础
状压和二进制有着不可割舍的联系
一帮情况下,(我)习惯把状态抽象成一维
并且用二进制表示,然后再去想怎么压缩状态
比如互不侵犯这个题
根据题目中给出的变量
直接定义dp[i][j][k]表示
第i行在状态为j时,共用了k个国王的方案数
那么如何判断上下左右有没有重复呢
这就用到二进制的运算&
然后预处理所有不必转移的状态和所有的状态
所有状态是指n位数的所有状态
例如n=3
所有状态就是
000,001,010,011,100,101,110,111
num数组记录当前状态的国王数量
第一能够减小常数
第二作为状态转移的初始状态
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
inline int read(){
int x = 0, w = 1;
char ch = getchar();
for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') w = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return x * w;
}
const int maxn = 520;
int dp[20][maxn][maxn];
int s[maxn], num[maxn];
int cnt, n, k;
inline void pre(){
cnt = 0;
for(int i = 0; i < (1 << n); i++){
if(i & (i << 1)) continue;
int sum = 0;
for(int j = 0; j < n; j++)
if(i & (1 << j)) ++sum;
s[++cnt] = i;
num[cnt] = sum;
}
}
signed main(){
n = read(), k = read();
pre();
dp[0][1][0] = 1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= cnt; j++)
for(int l = 0; l <= k; l++)
if(l >= num[j]){
for(int t = 1; t <= cnt; t++){
if(!(s[t] & s[j]) && !(s[t] & s[j] << 1) && !(s[t] & s[j] >> 1))
dp[i][j][l] += dp[i - 1][t][l - num[j]];
}
}
int ans = 0;
for(int i = 1; i <= cnt; i++)
ans += dp[n][i][k];
cout << ans << '\n';
return 0;
}
风吹过,我来过~