1087: [SCOI2005]互不侵犯King
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 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[n][k][i]; printf("%lld", ans); return 0; }