返回顶部

状态压缩DP-例题-炮兵阵地

洛谷传送门

这道题,通过题目可知,\(n\)\(m\)都比较小,可以想到当我们存储状态时,用二进制进行压缩


本题相当于要求在矩形网格中放多少个 “十字形状”,并且每个 “十字”的中心都不被其他它“十字”覆盖。我们采用按 “行号”为阶段的 DP 方法
为了不与之前相互冲突,所以我们需要知道第\(i-1\)和第\(i-2\)行的状态,设\(f[i][j][k]\)表示当第\(i\)行状态为\(j\),第\(i-1\)行状态为\(k\)时,前\(i\)行所能放的炮兵的最大数量
那么可知:

\[f[i][j][k]= \begin{cases}max(f[i-1][k][l]+count(j)\\-\infty \end{cases} \]

\(count(x)\)表示M为二进制数x中1的个数
\(valid(i,x)\)表示M位二进制数x属于集合S,并且x中的每个1对应在地图第i行中
的位置都是平原


记得看注释

首先,进行预处理:

int get_one(int x){
	int cnt=0;
	while(x) x&=(x-1),++cnt;
	return cnt;
}
bool ok(int x){
	if(x&(x<<1)) return false;
	if(x&(x<<2)) return false;
	return true;
}
void init(){
	idx=0;
	int end=1<<m;
	for(int i=0;i<end;i++)
		if(ok(i)){
			s[idx]=i;//s保存合法的方案 
			cnt0[idx]=get_one(i);//当每一行状态是i时找到该行1的个数,即炮兵个数 
			idx++; 
		}
}
bool vail(int i,int x){
	if(sg[i]&x) return false;//如果该位置既是高山,又放了炮兵,就不行 
	return true;
} 

然后进行处理:

int solve(){
	int ans=0;
	memset(dp,-1,sizeof(dp));
	dp[0][0][0]=0;//dp[i][j][k]表示第i行状态为,第i-1行状态为k是,前i行最多能放多少炮兵 
	for(int j=0;j<idx;j++){//n等于1时 
		if(vail(1,s[j])){//第一行能否采用方案j 
			dp[1][j][0]=cnt0[j];
			ans=max(ans,dp[1][j][0]);
		}
//=================================核心代码=============================================================
	for(int i=2;i<=n;i++){
		for(int j=0;j<idx;j++){//i行状态 
			if(vail(i,s[j])){
				for(int k=0;k<idx;k++){//i-1行状态
					if(vail(i-1,s[k])&&(s[j]&s[k])==0){//i与i-1都满足,同时不会冲突 
						int last=0;
						for(int l=0;l<idx;l++){//i-2行状态 
							if(dp[i-1][k][l]!=-1&&(s[l]&s[j])==0&&vail(i-2,s[l])){
								last=max(last,dp[i-1][k][l]);
							}
						}
						dp[i][j][k]=max(dp[i][j][k],last+cnt0[j]);
						if(i==n) ans=max(ans,dp[i][j][k]);
					}
				}
			}
		}
	}
//==============================================================================================
	return ans;
}

总代码:

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f;
int dp[101][77][77];
int sg[101];
int n,m,idx;
int s[77];
int cnt0[77];
int get_one(int x){
	int cnt=0;
	while(x) x&=(x-1),++cnt;
	return cnt;
}
bool ok(int x){
	if(x&(x<<1)) return false;
	if(x&(x<<2)) return false;
	return true;
}
void init(){
	idx=0;
	int end=1<<m;
	for(int i=0;i<end;i++)
		if(ok(i)){
			s[idx]=i;//s保存合法的方案 
			cnt0[idx]=get_one(i);//当每一行状态是i时找到该行1的个数,即炮兵个数 
			idx++; 
		}
}
bool vail(int i,int x){
	if(sg[i]&x) return false;//如果该位置既是高山,又放了炮兵,就不行 
	return true;
} 
int solve(){
	int ans=0;
	memset(dp,-1,sizeof(dp));
	dp[0][0][0]=0;//dp[i][j][k]表示第i行状态为,第i-1行状态为k是,前i行最多能放多少炮兵 
	for(int j=0;j<idx;j++){//n等于1时 
		if(vail(1,s[j])){//第一行能否采用方案j 
			dp[1][j][0]=cnt0[j];
			ans=max(ans,dp[1][j][0]);
		}
//==============================================================================================
	for(int i=2;i<=n;i++){
		for(int j=0;j<idx;j++){//i行状态 
			if(vail(i,s[j])){
				for(int k=0;k<idx;k++){
					if(vail(i-1,s[k])&&(s[j]&s[k])==0){//i与i-1都满足,同时不会冲突 
						int last=0;
						for(int l=0;l<idx;l++){//i-2行状态 
							if(dp[i-1][k][l]!=-1&&(s[l]&s[j])==0&&vail(i-2,s[l])){
								last=max(last,dp[i-1][k][l]);	
							}
						}
						dp[i][j][k]=max(dp[i][j][k],last+cnt0[j]);
						if(i==n) ans=max(ans,dp[i][j][k]);
					}
				}
			}
		}
	}
//==============================================================================================
	return ans;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=0;j<m;j++){
			char tmp;
			cin>>tmp;
			if(tmp=='H') sg[i]|=(1<<(m-1-j));//每一行如果有'H',就把二进制存储变为1 
		}
	init();
	cout<<solve()<<endl;
	return 0;
}

感谢观看


PS.
image

posted @ 2021-11-09 16:12  gyc#66ccff  阅读(30)  评论(0编辑  收藏  举报