nyoj85——炮兵阵地——————【状态压缩、动态规划】

/**

   结题思路:用dp[r][i][j]表示第r行r行状态为i,r-1行状态为j时的最大可部署炮兵的个数。通过求解合法状态,缩小需要遍历的状态范围。同时求出各个合法状态的二进制中1的个数,然后特殊处理首行,然后dp求解每行每种可行状态对应的可部兵的最大个数。状态转移方程dp[r][i][j]=max(dp[r][i][j],dp[r-1][j][k]+num_1[i])。

*/

#include<iostream>
#include<string.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
int n,m,top,num_legal;
char Map[110][15];
int dp[105][1<<6][1<<6];
//dp[r][i][j]表示第r行的状态为i,第r-1行的状态为j时的最多放的炮兵数
//可以发现虽然m值最大是10,但是合法的状态值最多60个
int an_Map[110];//将每行的图用状态值表示0代表原图为P,1代表H
int legal[1<<6];//将合法的状态放入数组,即不会互相攻击的状态
int num_1[1<<6];//记录每个合法状态值得二进制中1的个数
int max(int x,int y){

    return x>y?x:y;
}
bool jud_legal(int x){
    //相邻或只隔了一个位置为非法,相隔两个位置为合法
    if(x&(x<<1))return false;
    if(x&(x<<2))return false;
       return true;
}
int get_num(int x){
    //得到x的二进制中1的个数
    int tmp_num=0;
    while(x){

        if(1&x)
            tmp_num++;
        x>>=1;
    }
    return tmp_num;
}
void solve_legal(){

    top=1<<m;           //合法状态的最大值
    num_legal=0;        //合法状态的个数
    for(int i=0;i<top;i++){

        if(jud_legal(i)){   //判断该状态是否合法

            legal[num_legal]=i;
            num_1[num_legal++]=get_num(i);
        }
    }
}
void init(){

    memset(dp,-1,sizeof(dp));       //初始化为-1表示不可部兵
    memset(an_Map,0,sizeof(an_Map));
    memset(legal,0,sizeof(legal));
    memset(num_1,0,sizeof(num_1));
}
void solve_Map(){

    for(int i=1;i<=n;i++){

        for(int j=1;j<=m;j++){

            if(Map[i][j]=='H'){

                an_Map[i]+=(1<<(m-j));  //将原图的每行都用一个数值表示图状态
            }
        }
    }
}
int main(){

    int t;
    scanf("%d",&t);
    while(t--){

        scanf("%d%d",&n,&m);
        if(n==0||m==0){

            printf("%d\n",0);
            continue;
        }
        init();
        getchar();
        for(int i=1;i<=n;i++){

            gets(Map[i]+1);
        }
        solve_legal();  //处理出合法状态的数组
        solve_Map();    //处理出图的数字化表示数组
        for(int j=0;j<num_legal;j++){   //将第一行做特殊处理

            if((an_Map[1]&legal[j])==0){//如首行图不与该合法状态冲突
            //将该合法状态可部兵数赋给首行对应状态的dp数组
            //0行状态为0不会对下面的情况产生影响
                dp[1][j][0]=num_1[j];
            }
        }
        for(int r=2;r<=n;r++){

            for(int i=0;i<num_legal;i++){

                if(legal[i]&an_Map[r])  //r行合法状态与r行图冲突
                    continue;
                for( int j=0;j<num_legal;j++){

                    if(legal[i]&legal[j])continue;//r行合法状态与r-1行合法状态冲突
                    for(int k=0;k<num_legal;k++){

                        if(legal[k]&legal[i])continue;//r-2行合法状态和r行合法状态冲突
                        if(legal[k]&legal[j])continue;//r-2行合法状态和r-1行合法状态冲突
                        if(dp[r-1][j][k]==-1)continue;
                        //可以不加本语句。没更新过,不会对该dp值的最大值产生影响
                        dp[r][i][j]=max(dp[r][i][j],dp[r-1][j][k]+num_1[i]);
                    }
                }
            }
        }
        int ans=-1;
        for(int j=0;j<num_legal;j++){

            for(int k=0;k<num_legal;k++){
            //遍历所有状态求出最值
                ans=max(ans,dp[n][j][k]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值