洛谷P2704:炮兵阵地【状态压缩dp,滚动数组优化】【详细讲解+本菜的心路历程】

先看题;

很明显是状态压缩dp【因为本菜是在状态压缩dp专题里找到OVO】

先说一下本菜刚开始的想法吧;

本菜第一次对dp数组的定义是一个二维的;

f[i][j]表示状态第i行状态为j的最大炮兵数;

然后本菜的状态转移方程是这样的:

f[i][j]=max(f[i][j],f[i-2][k]+st[i-1].num+st[i].num)

其中st[i].num第i行状态的炮兵数,当然,在状态转移之前要先判断状态是否满足题意;

但是...这个方法是错误的,为什么呢?因为我们这边是直接加上一个第i-1行状态的炮兵数,却没考虑他和i-2行上面的行数是否满足条件;

所以本菜在卡了一段时间【”一段“】后,想到【”想“】了新dp数组的定义,定义如下:

f[i][j][k]为第i行状态为j,第i-1行状态为k的最大值;

然后状态转移方程是这样的:

f[i][j][k]=max(f[i][j][k],f[i-1][j1][k1]+st[i].num);

然后本菜发现,如果这样定义的话会爆空间,所以我们需要一个滚动数组优化,滚动数组优化的条件是一个状态只与之前计算出的状态有关,且这个状态在一个确定的区间内【比如上面这个只用到了i-2、i-1、i行的状态】;

下面就算我的详细注释了;

#include<iostream>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;

const int N=1<<10,M=101;

struct ch{
    int num,st;//num为状态中有多少个炮车,st为状态对应的十进制数
};

char map[M][M];//存储地图
int n,m;
vector<vector<ch>> list;//list[i]里存储着所有i行合法的状态
ll f[3][N][N];//考虑第i行,且第i行的状态为j,第i-1行状态为k的最大值
ll ans;

bool jud(int st,int x){//状态为st,行 为x
    
    for(int i=m;i>=1;i--){
        
        int a=st%2;//a为状态在对应字符中的位置的状态,如果a=1什么这个位置上有炮车
        if(a==1&&map[x][i]=='H') return 0;//如果在高地上有炮车,那么说明不符合条件
        st/=2;
    }
    
    return 1;
    
}

void init(){
    
    for(int i=1;i<=n;i++){//枚举每一行的状态
        
        for(int k=0;k<1<<m;k++){
            
            if(!jud(k,i)) continue;

            if((!((k<<1)&k))&&(!((k<<2)&k))&&(!((k>>1)&k))&&(!((k>>2)&k))){//判断一行内是否有可以是否会相互攻击

                int num=0,mid=k;//num存储k对应的状态有多少个炮车
                
                while(mid){
                    num+=mid%2;
                    mid/=2;
                }
                
                list[i].push_back({num,k});
                
            }
            
        }
        
    }
    
}

int main(){
    
    cin>>n>>m;
    
    
    for(int i=1;i<=n;i++){
        
        for(int j=1;j<=m;j++){
            
            cin>>map[i][j];
        }
    }
    
    if(n==1&&m==1&&map[n][m]=='P'){//这边要一个特判
        printf("1"); exit(0);
    }
    
    
    list.resize(N);
    
    init();
    
    for(int i=0;i<list[1].size();i++){//特殊枚举第1行
        
        f[1][list[1][i].st][0]=list[1][i].num;
        
    }
    
    for(int i=0;i<list[2].size();i++){//特殊枚举第2行
        
        for(int j=0;j<list[1].size();j++){
            
            if(list[1][j].st&list[2][i].st) continue;
            
            f[2][list[2][i].st][list[1][j].st]=f[1][list[1][j].st][0]+list[2][i].num;
            
        }
        
    }
    
    for(int i=3;i<=n;i++){
        
        for(int j=0;j<list[i].size();j++){//枚举第i行的状态
            
            for(int k=0;k<list[i-1].size();k++){//枚举第i-1行的状态
                
                for(int z=0;z<list[i-2].size();z++){//枚举第i-2行的状态
                    
                    //下面3行是判断i、i-1、i-2行是否有交集,即三行内的炮车是否可以互相攻击
                    if(list[i-2][z].st&list[i-1][k].st) continue;
                    
                    if(list[i][j].st&list[i-2][z].st) continue;
                    
                    if(list[i][j].st&list[i-1][k].st) continue;
                    //一个有点长的状态转移方程
                    f[i%3][list[i][j].st][list[i-1][k].st]=max(f[(i-1)%3][list[i-1][k].st][list[i-2][z].st]+list[i][j].num,f[i%3][list[i][j].st][list[i-1][k].st]);
                    
                }
                
            }
            
        }
        
    }
    
    for(int i=0;i<list[n].size();i++){
        
        for(int j=0;j<list[n-1].size();j++){
            
            if(list[n][i].st&list[n-1][j].st) continue;
            
            ans=max(ans,f[n%3][list[n][i].st][list[n-1][j].st]);
            
        }
        
    }
    
    cout<<ans;
    
    
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值