状态压缩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.