学习记录 状压DP(二)

例题3

这题与上面的题目类似,不过限制了可选的总数。令dp[i][j][k]表示前i行,状态为j,共计k个。按照之前的思路,我们选定了状态j后,这一行的“国王”总数就确定为j中二进制1的个数(记为popcnt(j))了,因此前i-1行还要k-popcnt(j)个。

代码如下:

int tot=(n<2)?2:(1<<(n-1))+(1<<(n-2));
for(int i=1; i<=n; i++) {
	for(int j=0; j<tot; j++) {
		if(j&(j>>1))  continue;
		for(int k=0; k<tot; k++) { //上一行
			if(!check(j,k)||k&(k>>1))    continue;
			int have=popCount(j);  //这一行选了几个
			for(int t=have; t<=m; t++) {  //总计要几个
				dp[i][j][t]+=dp[i-1][k][t-have];  //前i-1行还要选几个
			}
		}
	}
}

可以通过预处理所有合法状态并保存到数组中进行优化。
最终代码如下:

int tot=init(n); //合法状态总数
for(int i=1; i<=n; i++) {
	for(int j=0; j<tot; j++) {
		for(int k=0; k<tot; k++) { //上一行
			//时刻记住枚举的只是状态编号,stat中存的才是真实状态
			if(!check(stat[j],stat[k]))	continue;
			int have=popCount(stat[j]);  //这一行选了几个
			for(int t=have; t<=m; t++) {
				dp[i][stat[j]][t]+=dp[i-1][stat[k]][t-have];  //前i-1行还要选几个
			}
		}
	}
}

附:popCount函数可直接使用__builtin_popcount,但某些编译器不支持此函数 因此可以自行实现。下面的实现代码原理是不断去掉最低位的1,比朴素的O(logn)代码平均性能好。(毕竟其他方法也记不住…)

int popCount(unsigned int n) {
	int cnt=0;
	while(n>0) {
		cnt++;
		n&=(n-1);
	}
	return cnt;
}

例题4

由于本题每一行都会影响上两行的状态,一个二维数组似乎无法满足我们的要求,因此设dp[i][j][k]为前i行,当前状态为j,上一状态为k。很明显,我们需要4层循环。条件可以类比例题1得出。
因此:
dp[i][S0][S1]=max{dp[i][S1][S2]+popCount(S0)},Si为当前行前i行的状态。

然而本题如果直接暴力做时间复杂度高达 O ( n 2 3 m ) O(n2^{3m}) O(n23m),因此必须优化。由于有3层是枚举状态,因此我们可以从以下两个方面入手:
1.减少需要枚举的状态;
2.枚举状态内部进行的某些计算,同一状态要计算多次。可通过预处理,降为只用计算1次。
对于1,我们向上面一样预处理所有合法状态;对于2,我们可以预处理所有合法状态的popcount。

但大量的预处理导致空间可能不足。由于我太弱了,只能发现无论怎么选答案都不会超过SHORT_MAX,最大状态也不超过,因此只会将dp[][][]和sta[]改成short型。
注意要预处理第一行,因为第一行不用考虑前面的行。还应注意只有一行的特殊情况。

//预处理所有状态
int tot=initState(m);
for(int i=0; i<tot; i++) {
	pop[sta[i]]=popCnt(sta[i]);  //预处理所有popcount
}
//第一行
for(int i=0; i<tot; i++) {
	if(checkH(1,sta[i])) {
		dp[1][sta[i]][0]=pop[sta[i]];
		res=max(res,dp[1][sta[i]][0]); //可能少于2行!
	}
}
//永远记住sta[j]代表的是真实状态,而不是j本身!
for(int i=2; i<=n; i++) {
	for(int j=0; j<tot; j++) {  //this
		if(!checkH(i,sta[j]))    continue;
		short now=sta[j];
		for(int k=0; k<tot; k++) { //last
			if(!checkH(i-1,sta[k])||(now&sta[k]))    continue;
			for(int h=0; h<tot; h++) {  //last-last
				if(!checkH(i-2,sta[h])||(now&sta[h]))    continue;
				dp[i][now][sta[k]]=max(dp[i][sta[j]][sta[k]],(short)(dp[i-1][sta[k]][sta[h]]+pop[now]));
			}
			res=max(res,dp[i][sta[j]][sta[k]]);
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值