BZOJ 1087 - 状压dp

Magic Door

题目大意:

有一个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;
}
posted @ 2017-10-08 00:05  CzYoL  阅读(133)  评论(0编辑  收藏  举报