NOI2001 炮兵阵地

题目:link


容易想出状态 dp[t][i][j] ,表示当前在算第t行,这一行的状态为i,上一行的状态为j , 则状态转移方程就为 dp[t][i][j]=max{dp[t-1][j][k]}+pre[i] 其中k为上上行的状态,且i,j,k都满足题目要求,pre[i] 表示i在二进制下1的个数.

首先考虑读入与预处理。因为在山地不能放炮兵,所以枚举状态时要判断能不能放,在dp过程中用的是二进制来表示状态,所以读入的时候也把每一行的情况转化成二进制。如果是H,则当前位为1,否则为0,例如PHPPH就表示为01001。又考虑到pre[i]要被多次调用,所以先预处理出来。

预处理每行的状态(此处是从0到n-1)

for(rint i=0;i<n;i++){
    scanf("%s",s+1);
    for(rint j=1;j<=m;j++){
        a[i]<<=1;
        a[i]|=(s[j]=='H');
    }
}

预处理pre数组

inline int Get(int i){
    int sum=0;
    while(i){sum+=i&1;i>>=1;}
    return sum;
}

现在就来判断状态是否合法了

先考虑每一行自身的合法性:

1.炮兵不能放在山地上 , 所以如果 i&a[当前行数] 不为0就跳过。

2.每个炮兵在横向至少隔两格(否则就会互相打到),即 i&(i<<1)||i&(i<<2) 为false。

再考虑相邻的两行的合法性:

3.不能有炮兵同时在一列上,即 i&j 为0

最后考虑隔了一行的两行的合法性:

4.与3相同,炮兵不能同时在一列上,即 i&k 为0

考虑完后就可以开始状态转移了

#define rint register int
#define check(i,lin) (i&a[lin]||i&(i<<1)||i&(i<<2))

for(rint t=2;t<n;t++)
    for(rint i=0;i<p;i++){
        if(check(i,t)) continue;
        for(rint j=0;j<p;j++){
            if(check(j,t-1)||(i&j)) continue;
            for(rint k=0;k<p;k++){
                if(check(k,t-2)||(i&k)||(k&j)) continue;
                dp[t][i][j]=maxx(dp[t][i][j],dp[t-1][j][k]+pre[i]);
            }
        }
    }

然而分析一下空间复杂度$ 100*1024^2 $ ,愉快地炸掉了。再回去看代码,发现状态的第一维只与上一项有关,即t只和t-1有关,所以我们考虑压位,把第一维压掉。于是代码就变成了:

for(rint t=2;t<n;t++)
        for(rint i=0;i<p;i++){
            if(check(i,t)) continue;
            for(rint j=0;j<p;j++){
                if(check(j,t-1)||(i&j)) continue;
                for(rint k=0;k<p;k++){
                    if(check(k,t-2)||(i&k)||(k&j)) continue;
                    dp[t&1][i][j]=maxx(dp[t&1][i][j],dp[(t-1)&1][j][k]+pre[i]);
                }
            }
        }

完整代码:

#include<stdio.h>
#define rint register int
#define check(i,lin) (i&a[lin]||i&(i<<1)||i&(i<<2))
#define N 103
#define M 11

int n,m,a[N],pre[1<<M],dp[2][1<<M][1<<M];
char s[N];

inline int Get(int i){
    int sum=0;
    while(i){sum+=i&1;i>>=1;}
    return sum;
}
inline int maxx(int x,int y){return x>y? x:y;}
int main(){
    scanf("%d%d",&n,&m);
    int p=1<<m;
    for(rint i=0;i<n;i++){
        scanf("%s",s+1);
        for(rint j=1;j<=m;j++){
            a[i]<<=1;
            a[i]|=(s[j]=='H');
        }
    }
    for(rint i=0;i<p;i++) pre[i]=Get(i);
    if(n==1){
        int ans=0;
        for(rint i=0;i<p;i++)
            if(!check(i,0)) ans=maxx(ans,pre[i]);
        printf("%d",ans);
        return 0;
    }
    for(int i=0;i<p;i++){
        if(check(i,1)) continue;
        for(rint j=0;j<p;j++){
            if(check(j,0)||(i&j)) continue;
            dp[1][i][j]=pre[i]+pre[j];
        }
    }
    for(rint t=2;t<n;t++)
        for(rint i=0;i<p;i++){
            if(check(i,t)) continue;
            for(rint j=0;j<p;j++){
                if(check(j,t-1)||(i&j)) continue;
                for(rint k=0;k<p;k++){
                    if(check(k,t-2)||(i&k)||(k&j)) continue;
                    dp[t&1][i][j]=maxx(dp[t&1][i][j],dp[(t-1)&1][j][k]+pre[i]);
                }
            }
        }
    int ans=0;
    for(rint i=0;i<p;i++)
        for(rint j=0;j<p;j++)
            ans=maxx(ans,dp[(n-1)&1][i][j]);
    printf("%d",ans);
}

转载于:https://www.cnblogs.com/wwlwQWQ/p/10846413.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值