炮兵阵地-动态规划/状态压缩/位运算

炮兵阵地
问题来源:POJ-1185

Description
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

Input
第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。

Output
仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。

Sample Input
5 4
PHPP
PPHH
PPPP
PHPP
PHHP

Sample Output
6

先考虑这样一个简单的问题以便来分配数组的最大值(即10位情况下,布兵情况最大值),即1和1之间的间隔相差2以上的所有情况数:
<一>采用DFS深度优先搜索的策略求:
#include<iostream>
using namespace std;

int a[15] , ans=1;    //若把每个点都不布兵看做一种情况的话,就从1开始
void dfs( int now );

int main( ){

    dfs( 2 );    //规定布兵点标号从2到11(10个点),保证dfs中的i-2判断不会越界

    cout<<ans;    //最多可能情况

    return 0;
}

void dfs( int now ){
    for( int i=now ; i<=11 ; i++ ){
        if( !a[i-1] && !a[i-2] ){    //没有部署冲突
            a[i] = 1;    //该位置部署炮兵
            ans++;    //可能的情况累加
            dfs( i + 1 );    //继续搜索
            a[i] = 0;    //撤销布兵部署
        }
    }
}

<二>采用位机制求(二进制方法来解决,代码简洁,便于理解和调试):
#include<iostream>
using namespace std;

int main( ){
    int i , sum=0;
    for( int i=0 ; i<=(1<<10)-1 ; i++ ){    //遍历所有的情况,二进制表示从000..000到111...111
        if( (i&(i<<1)) || (i&(i<<2)) )    //移位运算,有重合的1,即不可以布兵
            continue;
        sum++;    //可以布兵,sum累加
    }

    cout<<sum<<endl;

    return 0;
}

输出答案是60,所以在写代码之前可以将下方代码的dp[MAX][MAX][MAX]改成dp[MAX][60][60],节省了部分空间.
啰嗦了半天,还是归根到底来求最大布兵数吧:

源代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int MAX = 102;
const int INF = 1<<30;
int N , M , sum;
int dp[MAX][MAX][MAX];
int stack[(1<<11)] , num[(1<<11)] , sit[MAX];
//dp[i][j][k]表示的含义是第i行数据取j这个状态,i-1行数据取k这个状态的最大布兵数
//stack保存可以布兵的所有情况,即上述所求的60种可行状态
//num[k]是k这种状态对应的二进制有多少个1
//sit[i]存储每行地势情况,平底(可以布兵)为0,便于位运算

void Init( void );
int  countNum( void );
int  ok( int x , int y );
int  max( int a , int b ){ return a >= b ? a : b; }

int main( ){
    char ch;

    while( cin>>N>>M ){
        Init( );
        getchar( );    //吞回车
        for( int i=1 ; i<=N ; i++ ){
            for( int j=1 ; j<=M ; j++ ){
                cin>>ch;
                if( ch=='H' ){
                    sit[i] += ( 1<<(j-1) );    //平底的位置可以布兵,标记为0
                }
            }
            getchar( );    //吞回车
        }

        cout<<countNum()<<endl;
    }

    return 0;
}

void Init( void ){
    sum = 0;
    memset( dp , 0 , sizeof( dp ) );
    memset( stack , 0 , sizeof( stack ) );
    memset( sit , 0 , sizeof( sit ) );
    memset( num , 0 , sizeof( num ) );

    for( int i=0 ; i<=(1<<M)-1 ; i++ ){

        if( (i&(i<<1)) || (i&(i<<2)) )    //有重合的1情况,略去
            continue;

        stack[sum] = i;

        int temp=i , s=0;

        while( temp ){    //求二进制中1的个数
            s++;
            temp &= ( temp - 1 );    //抹去二进制中最后一个1
        }

        num[sum++] = s;
    }
}

int  countNum( void ){
    int i , j , k , m , ans=-1;

    for( j=0 ; j<sum ; j++ ){
        if( ok( stack[j] , sit[1] ) ){    //如果j这种状态和1的地势状态想匹配,即该情况可以布兵
            dp[1][0][j] = num[j];    //记录dp的初始条件
            if( dp[1][0][j] > ans )
                ans = dp[1][0][j];
        }
    }

    for( i=2 ; i<=N ; i++ )    //从第二行开始
        for( j=0 ; j<sum ; j++ )    //遍历每一种布兵可行情况
            for( k=0 ; k<sum ; k++ ){    //遍历每一种布兵可行情况
                if( ok(stack[j],stack[k]) && ok(stack[k],sit[i]) && ok(stack[j],sit[i-1]) ){//考虑i-1行和i行布兵情况均没有冲突
                    for( m=0 ; m<sum ; m++ ){    //遍历每一种布兵可行情况
                        if( ok(stack[k],stack[m]) && ok(stack[j],stack[m]) && ok(stack[m],sit[i-2]) ){//考虑i-1行和i-1行和i行布兵情况均没有冲突(炮弹威力为2)
                            dp[i][j][k] = max( dp[i][j][k] , dp[i-1][m][j]+num[k] );
                            if( dp[i][j][k]>ans )
                                ans = dp[i][j][k];    //选区最大值
                        }
                    }
                }
            }
    return ans;    //返回最多布兵数
}

int  ok( int x , int y ){
    if( x&y )    //当x和y对应的二进制中有对应位置同时出现1,即不满足布兵要求
        return 0;
    return 1;
}

代码分析:我是参考别人的贴写的代码,真是相当佩服大牛们,这是我第一次写状态压缩的题,感觉这样的算法便于理解,不过得注意位运算和加减法的优先级顺序,适当加上括号防止不必要的错误;动态规划中的状态转移方程为dp[i][j][k]=max(dp[i-1][m][j]+num[k]),动归效率比DFS高,节省了绝大部分时间.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章