P1896 [SCOI2005]互不侵犯
题目描述
在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)
13(10)→→→1101(2)
我们可以得知哪些区域属于贫瘠土地或肥沃土地。
这便是状态压缩。
================================================
讲到二进制,就不得不讲一下位运算了,
因为一般涉及到状压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;
}