人生第一题状压DP(一边看资料一边学习过去),用状态f[i][j][k] 表示第i行状态是j,且前i行放置了k个棋子的方案数
则 f[i][j][k] = f[i - 1][p][k - c[j]]
枚举符合条件的j和p,其中我们用s[j]表示第j个状态,用c[j]表示状态j里1的个数(1对应棋子),s数组和c数组可以事先用dfs预处理
如何判断2个状态可以共存?
s[j] & s[p] == 0 也就是说这两个状态在同一位上不会同时为1,但是这还不够,题目说king可以攻击周围8个棋子,所以我们需要对对角线进行判断,其实很简单,我们把s[p] << 1 ,>>1就可以判断了,<<1相当于在最后补一个0,那么此时两个状态进行&时,s[j]相当于往右偏移一位,>>1同理,这样就把对角线也判断进去了,剩下的就没问题了
#include <set> #include <map> #include <list> #include <stack> #include <queue> #include <vector> #include <cmath> #include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; long long dp[15][1000][150]; long long s[5055]; long long c[5055]; int n; int res; bool is_ok(int a, int b) { if( s[a] & s[b] ) { return false; } if( (s[a] << 1) & s[b] ) { return false; } if( (s[b] << 1) & s[a] ) { return false; } return true; } void dfs(int l, int state, int cnt_1) { if(l == n) { s[++res] = state; c[res] = cnt_1; return ; } dfs(l + 1, state << 1, cnt_1); if( (state & 1) == 0 ) { dfs(l + 1, (state << 1) | 1, cnt_1 + 1); } } int main() { int k; while (~scanf("%d%d", &n, &k)) { res = 0; dfs(0, 0, 0); memset (dp, 0, sizeof(dp) ); dp[0][1][0] = 1; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= res; ++j) // 枚举第i行的状态 { for (int pn = c[j]; pn <= k; ++pn) //枚举前i行已经放置的总的棋子数 { for (int p = 1; p <= res; ++p) { if ( is_ok(j, p) ) { dp[i][j][pn] += dp[i - 1][p][pn - c[j]]; } } } } } long long ans = 0; for (int i = 1; i <= res; ++i) { ans += dp[n][i][k]; } printf("%lld\n", ans); } return 0; }