BZOJ 1087 - 状压dp
题目大意:
有一个n*n的棋盘,棋子可以攻击周围8个地方,求在棋盘中放入k个棋子且不会互相攻击的方案数有多少种。
题目分析
状态压缩: 首先将初始化每一行可能的情况(无用状态太多)存入State[].
dp[i][k][S]表示考虑到第i行,用了k个棋子,第i行状态为S的方案数。
\[dp[i][k][S] += dp[i - 1][k - cnt(S)][valid S']
\]
cnt(x)表示x二进制下1的个数。
1个数的求法:原理是不断删除末尾的1并计数。
inline int count(int x){
int ret;
for(ret = 0; x; ret++) x &= (x - 1);
return ret;
}
code
#include<bits/stdc++.h>
using namespace std;
const int N = 15, S = 1500;
int n, k;
int state[S], scnt, cnt[S];
typedef long long ll;
ll dp[N][N * N][S], ans;
inline int count(int x){
int ret;
for(ret = 0; x; ret++) x &= (x - 1);
return ret;
}
inline ll DP(int x, int p, int st){
if(x == 0) return p == 0 ? dp[x][p][st] = 1 : dp[x][p][st] = 0;
if(dp[x][p][st] != -1) return dp[x][p][st];
ll t = 0;
for(int i = 1; i <= scnt; i++){
if(cnt[i] > p - cnt[st] || (state[i] & state[st]) || ((state[i] << 1) & state[st]) || ((state[i] >> 1) & state[st])) continue;
t += DP(x - 1, p - cnt[st], i);
}
return dp[x][p][st] = t;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL), cout.tie(NULL);
cin >> n >> k;
for(int s = 0; s < (1 << n); s++){
int c = count(s);
if((s & (s << 1)) || c > k) continue;
state[++scnt] = s, cnt[scnt] = c;
// int l = s;
// while(l) cout<<l % 2, l /= 2; cout<<endl;
}
memset(dp, -1, sizeof dp);
for(int i = 1; i <= scnt; i++)
ans += DP(n, k, i);
cout << ans << endl;
}