由于答案可能会很大,不难想到使用状压dp解决。
考虑使用二进制来表示:
\[100010_{(2)} = 34_{(10)}
\]
这种访问方式比数组寻址更加简单快速,如 \((1 << (k - 1)) \& s\) 可以询问状态 \(s\) 的第 \(k\) 位上是 \(1\) 还是 \(0\)。\((j >> k) << k\) 可以把状态 \(j\) 的二进制表示下最右边几位清零,而数组不可以 \(O(1)\) 的时间复杂度清零。
考虑使用 \(f_{i, j, k}\) 表示前 \(i\) 行,第 \(i\) 行状态的二进制表示(十进制下的值)为 \(j\) ,放置了 \(k\) 个国王的方案数,那么状态转移方程:
\(f_{i, j, k} = \sum f_{i - 1, new, k - getlen(j)}\)
其中 \(new\) 表示所枚举的上一行的所有合法状态,\(getlen(j)\) 表示 \(j\) 状态有多少位为 \(1\)
code:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[20][205][1100], ans, n, K, m, nn, L;
int _get(int x) {int sum = 0;while(x) {if(x & 1) sum ++;x >>= 1;}return sum;
}
bool check(int x, int y) {if(x & y) return 1;if((x << 1) & y) return 1;if((x >> 1) & y) return 1;if((y >> 1) & y) return 1;return 0;
}
signed main() {cin >> n >> K;dp[0][0][0] = 1;nn = (1 << n) - 1;for(int i = 1;i <= n;i ++) {for(int j = 0;j <= K;j ++) {for(int k = 0;k <= nn;k ++) {L = _get(k);if(L > j) continue;if(k & (k >> 1)) continue;for(int l = 0;l <= nn;l ++) {if(check(k, l)) continue;dp[i][j][k] += dp[i - 1][j - L][l];}}}}for(int i = 0;i <= nn;i ++) {ans += dp[n][K][i];} cout << ans;
}