您的位置:首页 > 其它

bzoj1087 [SCOI2005]互不侵犯King

2016-12-17 10:36 246 查看

1087: [SCOI2005]互不侵犯King

Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 3306 Solved: 1915
[Submit][Status][Discuss]

Description

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

Input

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

Output

  方案数。

Sample Input

3 2

Sample Output

16
分析:这道题求方案数,那么可以想到用dp或者搜索,甚至还可以用数学方法,然而对于本题而言,搜索需要记录的信息和状态太多,会TLE,也没有什么很好的数学方法来解决这道题,所以只有采用dp.
注意到n非常小,而且状态需要记录的信息非常多,所以考虑状态压缩dp.对于这类棋盘问题,当前行的状态和上一行的状态有着密切联系,而且答案和k有关,所以我们设计状态dp[i][j][k]为前i行已经放了j个国王并且第i行的状态为k(二进制)的方案数,那么dp[i][j][k] = Σdp[i-1][j - num[k]][p],其中num数组记录着一行为状态k的放的国王的数目,p为上一行符合要求的状态.
现在的问题就是如何判断状态i是否符合要求,这不仅与i本身有关,还和它上一行的状态j有关,利用 <<,>>,&就可以判断了,它的原理是什么呢?我们把每个状态抽象成1个二进制数,它的第i位表示第i列放不放国王,利用&的性质,可以判断上下两行是否冲突,那么如何判断当前行是否冲突呢?将i左移1位再与i进行&操作即可.

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int n, k,num[1000];
long long dp[10][1000][1000];
bool flag[1000];
long long ans;

void init()
{
for (int i = 0; i < (1 << n); i++)
if (!(i & (i << 1)))
{
flag[i] = true;
int t = i;
while (t)
{
num[i] += (t & 1);
t >>= 1;
}
dp[1][num[i]][i] = 1;
}
}

int main()
{
scanf("%d%d", &n, &k);
init();
for (int i = 2; i <= n; i++)
for (int j = 0; j <= k; j++)
for (int now = 0; now < (1 << n); now++)
{
if (!flag[now])
continue;
if (num[now] > j)
continue;
for (int last = 0; last < (1 << n); last++)
{
if (!flag[last])
continue;
if ((last & now) || ((now << 1) & last) || ((now >> 1) & last))
continue;
dp[i][j][now] += dp[i - 1][j - num[now]][last];
}
}
for (int i = 0; i < (1 << n); i++)
ans += dp
[k][i];

printf("%lld", ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: