题目链接
题目
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
输入格式
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式
所得的方案数
输入样例
3 2
输出样例
16
题解
这是一道很经典的状态压缩dp的题目,基于连通性;
首先开始想状态转移方程,我们可以按行来枚举
f [ i , j , s] 表示已经摆好了前 i 行,并且第 i 行 的状态为 s,已经摆了 j 个国王的方案数
可以先预处理出来所有合法的方案,
- 1.对于同一行来说 ,要满足不存在两个连续的1(不然就会被攻击)
- 2.同时上下两行状态的与 必须等于0(没有并列的)同时上下两行或的状态也得是满足同一行没有两个连续的1才行
我们发现经过这样处理后第i - 1 行的状态与第 i 行的状态是成映射关系
这时候我们直接预处理每一行的前继状态有哪些,在方程转移时直接转移就可以了
最后算一下复杂度 总状态是i * j * s 也就是10 * 100 * 210 大概是106;
每次转移最多每个状态都转移也就是 210,总计应该是 109;
看似过不了,但其实我们枚举的状态有很多冗余状态,这里我们在预处理的时候已经判掉了,
经过后期程序测试发现, S状态 * 状态转移的大小基本上不会超过1365;
这样一算复杂度就只有一百万了,可以过,状压dp的很多题都是这样——至少得先写写试试吧
程序
#include <iostream> #include <cstring> #include <vector> #include <algorithm> using namespace std; const int N = 12,M = 1 << 10; const int K = 110; long long f[N][K][M]; vector<int> state; vector<int> head[M]; int cnt[M]; int n,k; bool check(int x) { for(int i = 0; i < n; ++ i) { if((x >> i & 1) && (x >> i + 1) & 1) return false; } return true; } int count(int x) { int res = 0; for(int i = 0; i < n; ++ i) res += x >> i & 1; return res; } int main() { cin >> n >> k; for(int i = 0; i < 1 << n; ++ i) if(check(i)) { cnt[i] = count(i); state.push_back(i); } for(int i = 0; i < state.size(); ++ i) for(int j = 0; j < state.size(); ++ j) { int a = state[i],b = state[j]; if((a & b) == 0 && check(a | b)) { head[i].push_back(j); } } f[0][0][0] = 1; for(int i = 1;i <= n + 1; ++ i) for(int j = 0; j <= k; ++ j) for(int a = 0; a < state.size(); ++ a) for(int b = 0; b < head[a].size(); ++ b) { int u = head[a][b]; int c = cnt[state[a]]; if(j >= c) f[i][j][a] += f[i - 1][j - c][u]; } cout << f[n + 1][k][0]; return 0; }