状压DP

状压DP

描述状态的维数,维度

首先找题目中在10~20范围内的参数(如果有这样的范围,那么这题就hin可能是状压),它很有可能是一个。

再看题目中的限制条件,它可能也要加进去

难点

状态转移 : 用二进制(或三进制或更⑥的)表示状态,用位运算状态的修改

初始化

常见操作

将x第i位变成1: x |= (1<<(i-1))

将x第i位变成0 : x &= (~(1<<(i-1)))

取出x第i位: (1<<(i-1))&x

取出x最靠右的1: lowbit(x) = x&(-x)

取出x最靠右的0:xx = (~x & 位数), 取xx的lowbit

依次取出一个数的所有的 1 : 取一个lowbit, 减一个lowbit

依次取出一个数的所有的 0 : 同理,取一个0,减一个0(对xx操作

枚举非空子集: for(int x = sta ; x ; x = ((x-1) & sta) )

栗提(我没有状压基础,所以觉得题都挺好的

[SCOI2005]互不侵犯

https://www.luogu.org/problem/P1896

这种求合法的方案的题目,一般需要对与当前状态相契合的合法方案数求和

首先这题肯定是压N, 然后有国王数这个限制,而且前一行的状态也是个限制,之后综合起来,考虑状态的转移,注意怎么初始化和统计答案。

#include<cstdio>
using namespace std;
const int MAXN = 20;
#define ll long long

int n, KK;
//f[i][s][k]表示前i行(包括第i行),第i行的状态为s,已选k个国王的方案 
//f[i][s][k] = Σf[i-1][s'][k - k_num[s]] ,即上一行与当前行相契合的方案都要加进去 
ll f[20][1024][40]; 
int MAX, cnt, can[100], k_num[100];

int main() {
	scanf("%d%d",&n, &KK);
	int MAX = (1<<n)-1;
	int sum;
	int tmp;
	for(int i = 0; i <= MAX; ++i) {//预处理每一行的合法方案和它对应的国王个数 //预处理要全面!!从0开始 
		if(i&(i<<1)) continue;//把同行不合法的去掉
		can[++cnt] = i;
		sum = 0;
		tmp = i;
		while(tmp) sum += (tmp&1), tmp = tmp>>1;
		k_num[cnt] = sum;
	}
	for(int j = 1; j <= cnt; j++) f[1][j][k_num[j]] = 1;//初始化? 
	for(int i = 2; i <= n; i++) {//枚举i,s,s'(这里的j),求和 
		for(int s = 1; s <= cnt; s++) {
			for(int j = 1; j <= cnt; j++) {
				if((can[s]&can[j]) || ((can[s]<<1)&can[j]) || ((can[s]>>1)&can[j])) continue;//判断是否和前一行冲突(考虑国王的影响范围后易得)
				for(int k = k_num[s]; k <= KK; k++) f[i][s][k] += f[i-1][j][k - k_num[s]];//枚举k 
			}
		}
	}
	long long ans = 0;
	for(int j = 1; j <= cnt; j++) ans += f[n][j][KK];//统计答案 
    printf("%lld",ans);
	return 0;
}

JDOJ 1009 五子棋

https://neooj.com:8082/oldoj/problem.php?id=1099

题目大意
有N个人在进行五子棋比赛,每个人都有一 个初始经验值st_exp_i。若i,j比赛一场,i会获 得exp_i,j的经验值,j会获得exp_j,i的经验值, 当前经验值高的人获胜,获胜者会得到sco_i,j 的分数,现在1号队员可以安排比赛顺序,他 希望自己得分最高,请问他最高可以得多少 分。

数据范围 : 1<=N<=13

首先一个贪心,只需要求1号的最高得分,所以我们只需要考虑1号和剩下n-1个人的比赛,并且还要先考虑,因为他们在变 强。这样我们就可以只压一维的N了。

因为经验与顺序无关,所以可以根据状态直接算出1号的经验,所以不用再开一维,当状态不好表示时,试着增加维度。(即与顺序有关那就hin难了)

#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN = 14;

int n, T;
int st_exp[MAXN], exp[MAXN][MAXN], sco[MAXN][MAXN];
int f[1<<MAXN];
int MAX;

void init() {
	for(int i = 1; i <= n; i++) 
		for(int j = 1; j <= n; j++) scanf("%d",&exp[i][j]);
	for(int i = 1; i <= n; i++) 
		for(int j = 1; j <= n; j++) scanf("%d",&sco[i][j]);
	for(int i = 1; i <= n; i++) scanf("%d",&st_exp[i]);
}

int getexp(int s) {
	int exp1 = st_exp[1];
	for(int i = 1; i <= n; i++) if(s&(1<<(i-1))) exp1 += exp[1][i+1];
	return exp1;
}

int main() {
	scanf("%d",&T);
	while(T--) {
		memset(f, 0, sizeof(f));
		scanf("%d",&n);
		init();
		n--;
		//考虑安排先把前n-1个人安排着和1号vs 
		MAX = (1<<n)-1;
		int exp1;//根据状态直接算出1号的经验
		for(int s = 0; s <= MAX; s++) {
			exp1 = getexp(s);
			for(int j = 1; j <= n; j++) {
				if(s&(1<<(j-1))) continue;
				if(exp1 > st_exp[j+1]) f[s|(1<<(j-1))] = max(f[s|(1<<(j-1))], f[s] + sco[1][j+1]);
				else f[s|(1<<(j-1))] = max(f[s|(1<<(j-1))], f[s]);
			}
		}
		printf("%d
",f[(1<<n)-1]);
	}
	return 0; 
}

[USACO06NOV]玉米田Corn Fields

https://www.luogu.org/problem/P1879

和上面的互不侵犯一样

#include<cstdio>
#include<algorithm>
using namespace std;
const int mod = 100000000;

int n,m, ans;
int f[13][1000];//f[i][j]: 第i行选用状态can[i].st[j]这个状态时,方案数 
struct node{
	int num;
	int st[5000];
}can[13];//can[i]:第i行所有合法的状态,及状态数 

void getstate(int hi, int t) {
	int num = 0;
	for(int i = 0; i < (1<<m); i++) {//i==0 ->可以不种草 
		if((i&(i<<1)) || (i&(i>>1)) || (t&i)) continue;//t&i==1 -> 非法种草 
		can[hi].st[++num] = i;
	}
	can[hi].num = num;
} 

void init() {//初始化每一行合法方案  
	int t, x;
	for(int i = 1; i <= n; i++) {
		t = 0;
		for(int j = 1; j <= m; j++) {
			scanf("%d",&x);
			t = (t<<1)+1-x;//把这一行的状态弄成十进制,注:1表示不能种草(这样方便判断种草的位置是否合法)
		}
		getstate(i, t);
	}
}

int main() {
	scanf("%d%d",&n,&m);
	init();
	for(int j = 1; j <= can[1].num; j++) f[1][j] = 1;//初始化第一行 
	for(int i = 1; i < n; i++) 
		for(int j = 1; j <= can[i].num; j++)
			for(int k = 1; k <= can[i+1].num; k++) {//刷表法 
				if(can[i+1].st[k] & can[i].st[j]) continue;
				else f[i+1][k] += f[i][j];
			}
//	for(int i = 2; i <= n; i++) 
//		for(int j = 1; j <= can[i].num; j++) {
//			f[i][j] = 0;//填表法比上面的多个初始化 
//			for(int k = 1; k <= can[i-1].num; k++) {
//				if(can[i].st[j]&can[i-1].st[k]) continue;
//				else f[i][j] += f[i-1][k];
//			}
//		}
	for(int j = 1; j <= can[n].num; j++) ans = (ans+f[n][j]) %mod;
	printf("%d
",ans);
	return 0;
}

这里要注意的一个细节

我们在做这种有的位置是障碍的题目(如上题的坏田,如下题的高地)时,我们可以把障碍处填1可以放东西的地方填0,这样,我们一个&就可以判断当前的位置是否可以放东西进而求出每行合法的状态。要注意操作是<<左移,想想我们判断的数 的二进制

[NOI2001]炮兵阵地

https://www.luogu.org/problem/P2704

题目大意
给出一张n*m的地图,其中有一些是平原,一些是高地,现在有一些炮兵要在这块地图上进行部署,炮兵无法部署在高地上,同时他们不能够互相攻击
问地图上最多能部署多少炮兵部队。
右图黑块为灰块可
数据范围
N≤100,M≤10

错误代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;

int n,m,debug;
int f[3][60][60];//f[i][st1][st2]:当前为第i行,第i行状态为st[st1], 第i-1行状态为st[st2]时的最大方案数 
//影响答案的只有三行 

struct node{
    int num, st[60];
    int num_s[60];//状态st[i]所拥有的士兵个数为num_s[i] 
}can[100+9];

int getone(int x) {
    int num = 0;
    while(x) x -= x & -x, num++;
    return num;
}
void getstate(int hang, int st) {
    int num = 0;
    for(int i = 0; i < (1<<m); i++) {
        if( (i&(i<<1)) || (i&(i<<2)) || (i&(i>>1)) || (i&(i>>2)) || (i&st)) continue;
        can[hang].st[++num] = i;
        can[hang].num_s[num] = getone(i);
    }
    can[hang].num = num;
}
void init() {//get每行合法的情况 
    string s;
    int t, tmp;
    for(int i = 1; i <= n; i++) {
       	cin>>s;
        t = 0;
        for(int j = 0; j < m; j++) {
            tmp = s[j]=='H' ? 1 : 0;
            t = (t<<1)+tmp;//山地不放
        }
//        if(debug == 2) printf("第%d行的t值: %d
", i, t);
        getstate(i, t);
    }
}

void pre() {//初始化一二行 
    for(int j = 1; j <= can[1].num; j++) f[1][j][0] = can[1].num_s[j];
    for(int j = 1; j <= can[2].num; j++) 
        for(int k = 1; k <= can[1].num; k++) {
            if(can[2].st[j]&can[1].st[k]) continue;
            f[2][j][k] = can[2].num_s[j] + can[1].num_s[k];
        }
}

int main() {
//	freopen("02.in", "r", stdin);
//	freopen("02.out", "w", stdout); 
	debug = 1;
    scanf("%d%d",&n,&m);
    init();
    pre();
//    if(debug == 1) { 
//	    for(int i = 1; i <= n; i++) {
//			printf("第%d行的合法情况如下, 共%d个合法情况
",i, can[i].num);
//			for(int j = 1; j <= can[i].num; j++) printf("士兵数:%d ; 状态: %d 
", can[i].num_s[j] ,can[i].st[j]);
//		} 
//	}
    for(int i = 3; i <= n; i++) 
    	for(int j = 1; j <= can[i].num; j++) 
    		for(int k = 1; k <= can[i-1].num; k++) {//士兵数从上往下只增不减,所以不用初始化f[i][j][k]
				if(can[i].st[j]&can[i-1].st[k]) continue;//填表法 
				for(int up2 = 1; up2 <= can[i-2].num; up2++) {
					if(can[i-2].st[up2]&can[i-1].st[k] || can[i-2].st[up2]&can[i].st[j]) continue;
					f[i%3][j][k] = max(f[i%3][j][k], f[(i-1)%3][k][up2]+can[i].num_s[j]);
				}
			}
	int ans = 0;
	for(int j = 1; j <= can[n].num; j++)
		for(int k = 1; k <= can[n-1].num; k++) {
			if(can[n].st[j]&can[n-1].st[k]) continue;//??? 
			ans = max(ans, f[n%3][j][k]);
		}
	printf("%d
",ans);
	//为什么只用前一个和当前的:我们只需要这样就可以表示出所有要用的状态了 
	return 0; 
}

例题就到这里哈,有兴趣的可以看看标签

原文地址:https://www.cnblogs.com/tyner/p/11391500.html