题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
输入输出格式
输入格式:
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式:
所得的方案数
输入输出样例
3 2
16
分析:
状压dp
我们的三个状态就有:第几行(用i表示)、此行放什么状态(用j表示)、包括到这一行为止已经使用了的国王数(用s表示)。
考虑状态转移方程。我们预先处理出每一个状态(sit[x])其中包含二进制下1的个数,及此状态下这一行放的国王个数(gs[x]),于是就有:
f[i][j][s]=sum(f[i-1][k][s-gs[j]])f[i][j][s]=sum(f[i−1][k][s−gs[j]]) ,f[i][j][s]f[i][j][s] 就表示在只考虑前i行时,在前i行(包括第i行)有且仅有s个国王,且第i行国王的情况是编号为j的状态时情况的总数。而k就代表第i-1行的国王情况的状态编号
其中k在1到n之间,j与k都表示状态的编号,且k与j必须满足两行之间国王要满足的关系。(对于这一点的处理我们待会儿再说)
这个状态转移方程也十分好理解。其实就是上一行所有能够与这一行要使用的状态切合的状态都计入状态统计的加和当中。其中i、j、s、k都要枚举。
再考虑国王之间的关系该如何处理呢?在同一行国王之间的关系我们可以直接在预处理状态时舍去那些不符合题意的状态,而相邻行之间的关系我们就可以用到一个高端的东西:位运算。由于状态已经用数字表示了,因此我们可以用与(&)运算来判断两个状态在同一个或者相邻位置是否都有国王——如果:
sit[j]&sit[k]sit(及上下有重复的king)
(sit[j]<<1)(sit[j]<<1) &sit[k] (及左上右下有重复king)
sit[j] &(sit[k]<<1)(sit[k]<<1) (及右上左下有重复king)
这样就可以处理掉那些不符合题意的状态了。
总结一下。其实状压DP不过就是将一个状态转化成一个数,然后用位运算进行状态的处理。理解了这一点,其实就跟普通的DP没有什么两样了。
最后上代码(注意其中的一些细节处理):
#include <bits/stdc++.h> using namespace std; #define maxn 100000 typedef long long ll; #define inf 2147483647 #define ri register int int n, tot; int cnt = 0; int sit[maxn], sum[maxn]; ll dp[10][2000][1025]; //注意要开ll ll ans = 0; void dfs( int p, int s, int cur) { // dfs搜索得到单行的所有不相邻方案排列sit[]此行的国王数sum[],还有总方案数cnt if (cur >= n) { sit[++cnt] = p; sum[cnt] = s; return; } dfs(p, s, cur + 1); dfs(p + (1 << cur), s + 1, cur + 2); } int main() { ios::sync_with_stdio(false); // freopen("test.txt", "r", stdin); // freopen("outout.txt","w",stdout); cin >> n >> tot; dfs(0, 0, 0); for (int i = 1; i <= cnt; i++) dp[1][i][sum[i]] = 1;//初始化 for (int i = 2; i <= n; i++) for (int j = 1; j <= cnt; j++)//当前行状态 for (int k = 1; k <= cnt; k++) {//前一行状态 if (sit[j] & sit[k]) continue; if ((sit[k] >> 1) & sit[j]) continue; if ((sit[k] << 1) & sit[j]) continue; for (int s = sum[j]; s <= tot; s++) dp[i][j][s] += dp[i - 1][k][s - sum[j]]; } for (int i = 1; i <= cnt; i++) ans += dp[n][i][tot]; cout << ans; return 0; }