P2704 [NOI2001] 炮兵阵地-状压dp题解

1前言

本文是题解,不会介绍什么是状压dp,萌新请进我主页有介绍状压dp
作者是蒟蒻,提供的思路及标程没有滚动数组等优化,但是足以在洛谷评测机通过

2问题

如下图
在这里插入图片描述
在这里插入图片描述

翻译一下吧,就是在n*m的方格子里,摆放尽可能多形式如下的十字形物品
00100
00100
11111
00100
00100

可以超出边界,但是互相之间不可以冲突,有些格子不能放,求最多能放多少
这个数据范围,这用背包有后效性,就差把状压dp写题面里了…

3状态设置

我们先考虑直接压,n行m列,但是我们可以发现n非常大,以至于不压m列,只压n行都存不下了
但是m好像小一些
我们舍弃n行,直接用一维数组存n
我们还可以发现,只有上两行的会影响当前行
但是好像压缩三行也有点困难…
因为n作为数组的一维,我们可以只压两行,再用上一行推前两行,这就有递推性质,就这么定了

4属性及转移

属性显然为MAX,求最大值
我们发现,前两行的不同情况都对当前情况有影响
我们枚举三行,不要忘了n
设当前行为i,当前行状态为j,前一行状态为k,前两行状态为l,i二进制位1的个数为count(i)
得状态转移方程dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][l]+count(i));
这时间复杂度大约2^36,能过才怪

5优化

不要忘了不是所有状态均合法,所以我们还有需要特判的步骤没做,在这一步优化吧
我们一步一步优化

优化1

不是所有位置都可以摆放炮兵
我们读入的是字符串,转换为01串,1为山地,然后直接状压
当前状态1为选择,而山地又不能选择,这下可以用与运算了,与出来不为0就跳过,且本方法对三行都适用

优化2

三行中,每个炮兵没有两个在同一列,我们依旧与运算,同上,与出来不为0就跳过,又快了好多

优化3

每一行的炮兵之间间隔至少为2

(1)作者方法

我考虑了lowbit,将每行lowbit算出,相邻之间,设小的为i,大的为j
i左移两位,如果相等或i>j,距离就小于2了
作者是树状数组学魔怔了,还有更快的方法

(2)常规方法

设当前行状态压缩后为i
我们用左移,分别将i左移一位和左移两位,都与一下,不为0就跳过
左移的本质就是将二进制位向左移动,而且是所有二进制位,一次成型,推荐这种方法,常数小
附代码(c++);

#include<bits/stdc++.h>
using namespace std;
const int N = 118,M = (1<<10)+200;
int dp[N][M][M];//本行和上一行,具有递归性质,时间空间乘上两个2^10=2^20
int n,m,ans;
string s;
int a[N];
int lowbit(int x){
	return x&-x;
}
bool judge(int x){//优化3 
	int k = x;
	int c1 = lowbit(k),c2;
	k-=c1;
	while(k!=0){
		c2 = lowbit(k);
		if((c1<<2)>=c2){
			return false;
		}
		k-=lowbit(k);
		swap(c1,c2);
	}
	return true;
	
}
int count(int x){
	int k = x,sum = 0;
	while(k!=0){
		sum+=(k&1);
		k>>=1;
	}
	return sum;
}
int main(){
	cin>>n>>m;
	for(int i = 1;i<=n;i++){
		cin>>s;
		for(int j = 1;j<=m;j++){//优化1.对地形数组压缩 
			if(s[j-1]=='H'){
				a[i]+=1<<(j-1);
			}
		}
	}
	for(int i = 1;i<=n;i++){
		for(int j = 0;j<(1<<m);j++){
			for(int k = 0;k<(1<<m);k++){
				if((!judge(j))||(!judge(k))||((j&k)!=0)||((j&a[i])!=0)||((k&a[i-1])!=0)){//优化2直接体现在if里 
					continue;
				}
				for(int l = 0;l<(1<<m);l++){
					if(((l&j)!=0)||((l&k)!=0)||(!judge(k))){
						continue;
					}
					dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][l]+count(j));
				}
				
			}
		}
	}
	for(int i = 0;i<(1<<m);i++){
		for(int j = 0;j<(1<<m);j++){
			ans = max(ans,dp[n][i][j]);
		}
	}
	cout<<ans;
	return 0;
}

6后记

作者认为,只要位运算学的好,状压dp差不了,高时间复杂度可以用位运算的小常数来平衡
有的位运算甚至能省一轮循环!(如上文优化3中常规方法)
本蒟蒻自己做蓝题很困难,很有可能会产生错误,请各位神犇指点
森林古猿出品,必属精品,请认准CSDN森林古猿1

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值