[SCOI2005]互不侵犯

[SCOI2005]互不侵犯

状压DP练习题

我们先审个题:

  首先注意这道题的数据范围

(1leq N leq 9 , 0 leq Kleq N^2)

是不是真的很小啊,所以我们考虑用状压DP或爆搜的办法解。

解法:

  这道题求方案数,那么可以很自然的想到用dp或者搜索,然而对于本题而言,搜索需要记录的信息和状态太多,会T到天上去,所以只有采用状压DP来解决。
   注意到n非常小,而且状态需要记录的信息非常多,所以考虑状态压缩dp。在这道题里,当前行的状态和上一行的状态有着密切联系,而且答案和k有关,所以我们设计状态f[i][j][k]为前i行已经放了j个国王并且第i行的状态为k(二进制)的方案数,那么(f[i][j][k] = sum f[i - 1][j - num[k]][l]),其中num数组记录着一行为状态k的放的国王的数目,l为上一行符合要求的状态.
现在的问题就是如何判断状态i是否符合要求,这不仅与i本身有关,还和它上一行的状态j有关,利用左移,右移,&就可以判断了,它的原理是什么呢?我们把每个状态抽象成1个二进制数,它的第i位表示第i列放不放国王,利用&的性质,可以判断上下两行是否冲突,那么如何判断当前行是否冲突呢?将i左移1位再与i进行&操作即可。

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

#define N 1100
#define M 15
#define ll long long

using namespace std;

int n,k,num[N];
ll f[M][N][N],ans;
bool flag[N];

int main() {
	scanf("%d%d",&n,&k);
	for(int i = 0 ; i < (1<<n) ; i++) {
		if(!(i & (i<<1))) {
			flag[i] = 1;
			int t = i;
			while(t) {
				num[i] += (t & 1);
				t >>= 1;
			}
			f[1][num[i]][i] = 1;
		}
	}
	for(int i = 2 ; i <= n ; i++)
		for(int j = 0 ; j <= k ; j++)
			for(int l = 0 ; l < (1<<n) ; l++) {
				if(!flag[l]) continue;
				if(num[l] > j) continue;
				for(int last = 0 ; last < (1<<n) ; last++) {
					if(!flag[last]) continue;
					if((last & l) || (l<<1)&last || (l>>1)&last) continue;
					f[i][j][l] += f[i - 1][j - num[l]][last];
				}
			}
	for(int i = 0 ; i < (1<<n) ; i++)
		ans += f[n][k][i];
	printf("%lld",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/Repulser/p/9609402.html