poj 1185 炮兵布陣


狀態壓縮DP(使用位運算加速)

這是個經典的狀態壓縮DP,為加深印象詳細寫寫一下報告,由於是中文題目所以不說題意了

思考方法:首先,一個炮的攻擊有兩行,所以對於第i行來講,i-1行和i-2行對它有影響,i-3行及以上的都沒有影響了,所以我們要得到第i行的信息,只需要知道i-1和i-2的信息(最近有個體會,DP要找到什么因素影響了當前你要求的東西,有影響的我們就處理,沒影響的我們不用管)。接着我們就思考怎么表示狀態。山用1表示,空地用0表示,空地放了兵也用1表示,那么對於一行,就是一個01的串,這是個二進制數,我們可以想到狀態壓縮壓縮回來一個十進制數。

比如原地圖01101011,那么0處可以放兵,所有那么多個0,可以變為1(但也要考慮炮與炮之間不能攻擊),要枚舉全部情況,我們很自然想到了dfs來枚舉,很多解題報告是這樣做的,這樣確實也能解決,但不是最好的方法。最好的方法是位運算。

要想到位運算,要跳出思維的限制,在一行中,原有的1是固定不能變,炮不能放在山上。0是可以變為1的,但是要保證炮與炮之間不能攻擊。要滿足這兩個要求,我們可以拆開來做。先滿足了炮與炮不能互相攻擊,然后在這些擺放中再選出跑不在山上的。

只考慮炮的話,枚舉量2^10-1,但實際上滿足的不足60個,網上有人問過60是怎么計算的,實際上准確的做法我也不確定,但是能大概推出來。一列最多10位,最多其實只能放4個炮,然后接着看3個炮,2個炮,1個炮,0個炮的情況,就可以大致算出。

枚舉方法是   for(i=0; i<(i<<col); i++)  if( !(i&(i<<1)) & !(i&(i<<2)) )  i是合法狀態   這個是要點,要理解    

int state[MAXM];  保存狀態(十進制數),是僅僅滿足了炮與炮不互相攻擊,但是沒有滿足炮不在山上

對於一開始的地圖,還沒放炮,它本身已經表示了一個狀態,所以也先壓成一個狀態,保存在一個數組中

再者,我們得到了一個狀態state[i],我們怎么知道這個狀態下放了多少炮啊?其實就是判斷state[i]這個十進制數變為二進制數后有多少個1,這個要怎么統計呢?位運算!

並且把對應的士兵人數保存在  int soldier[MAXM]; //對應着,在state[i]狀態下能放多少個士兵

int base[MAXR];  //第i行的原地圖壓縮成的一個狀態

那么怎么判斷炮不在山上呢?  只要state[i] & base[r] = 0 ,就表示state[i]這個狀態,可以放在r這行上,而且炮不會在山上,炮之間也不會攻擊,這是個要點,理解

 

然后前面說了,i行,i-1行,i-2行的炮會互相影響,他們可能會互相攻擊到對方,所以我們假設現在i行,i-1行,i-2行的炮的擺放情況分別是state[i],state[j],state[k]

只有當他們都不兩兩攻擊的時候,這3個狀態才能放在一起,否則這3個狀態不能放在一起。那么怎么判斷他們不會兩兩攻擊呢,方法一樣的

state[i] & state[j] = 0   state[i] & state[k] = 0     state[j] & state[k] = 0   ,三個要同時滿足,要點,理解

你會發現多次需要用到 一種判斷  a&b  是為0還是不為0,所以我們代碼中將其寫成宏定義方便查看,實際上寫成宏后時間慢了一點

#define legal(a,b) (a&b) //判斷兩個狀態共存時是否合法,合法為0,不合法為非0 

 

最后看狀態轉移方程,設dp[r][i]表示第r行,狀態為state[i]是的最大值,

dp[r][i]=max { dp[r-1][j]+dp[r-2][k] } + soldier[i]

也就是第r-1行的狀態為state[j],第r-2行的狀態為state[k]的和,並加上當前行放了soldier[i]個士兵 , 但要滿足state[i],state[j],state[k]不能互相攻擊

接着我們可以可以寫成三維的形式 dp[r][i][j]= max{ dp[r-1][j][k]} + soldier[i]  ,  dp[r][i][j]表示第r行狀態為state[i],第r-1行為state[j]的最大值

 

然后可以看代碼了

 

#include <cstdio>
#include <cstring>
#define MAXR 110 //行數
#define MAXC 15  //列數
#define MAXM 70  //狀態數
#define max(a,b) a>b?a:b //返回較大值
#define CL(a) memset(a,0,sizeof(a)) //初始化清空數組
#define legal(a,b) a&b //判斷兩個狀態共存時是否合法,合法為0,不合法為非0

int row,col;  //行列
int nums;  //僅是兩個炮兵不互相攻擊的條件下,符合條件的狀態個數
int base[MAXR];  //第i行的原地圖壓縮成的一個狀態
int state[MAXM]; //僅是兩個炮兵不互相攻擊的條件下,符合條件的狀態(一個十進制數)
int soldier[MAXM]; //對應着,在state[i]狀態下能放多少個士兵
int dp[MAXR][MAXM][MAXM];
//dp[i][j][k] 表示第i行狀態為state[j],第i-1行狀態為state[k]時的最優解
char g[MAXR][MAXC];

int main()
{
    CL(base); CL(state); CL(soldier); CL(dp);
    nums=0;

    scanf("%d%d",&row,&col);
    for(int i=0; i<row; i++)  //先計算原始地圖的狀態數
    {
        scanf("%s",g[i]);
        for(int j=0; j<col; j++)
        if(g[i][j]=='H') base[i]+=1<<j; //像0110000,這里計算為6
    }

    for(int i=0; i<(1<<col); i++)  //僅是兩個炮兵不互相攻擊的條件下計算所有狀態
    {
        if( legal(i,i<<1) || legal(i,i<<2)) continue; //i這個狀態出現了士兵兩兩攻擊
        int k=i;
        while(k) //這個循環計算狀態i的二進制形式里面有多少個1,也就是放了多少個士兵
        {
            soldier[nums]+=k&1; //等價於k%2,相當於判斷k的二進制形式里面有多少個1
            k=k>>1;
        }
        state[nums++]=i; //保存這個合法的狀態
    }

    /***************************************/
    //for(int i=0; i<nums; i++) printf("%d %d\n",state[i],soldier[i]);
    /***************************************/

    for(int i=0; i<nums; i++) //先初始化dp[0][i][0],即初始化第1行的情況
    {
        if(legal(state[i],base[0])) continue;
        //在state[i]的基礎上,還要滿足士兵不能放在山上,這個判斷就是處理這個問題的
        dp[0][i][0]=soldier[i];
    }
    for(int i=0; i<nums; i++) //接着初始化dp[1][i][j],即第2行的情況
    {
        if(legal(state[i],base[1])) continue;
        for(int j=0; j<nums; j++) //枚舉第1行的狀態
        {
            if(legal(state[j],base[0])) continue;
            if(legal(state[i],state[j])) continue;
            dp[1][i][j]=max(dp[1][i][j] , dp[0][j][0]+soldier[i]);
            //狀態轉移方程
        }
    }

    for(int r=2; r<row; r++) //第3行開始DP直到最后
        for(int i=0; i<nums; i++) //枚舉第r行的狀態
        {
            if(legal(state[i],base[r])) continue;
            for(int j=0; j<nums; j++) //枚舉第r-1行的狀態
            {
                if(legal(state[j],base[r-1])) continue;
                if(legal(state[i],state[j])) continue;
                //第r行的士兵和第r-1行的士兵相互攻擊
                for(int k=0; k<nums; k++) //枚舉第r-2行的狀態
                {
                    if(legal(state[k],base[r-2])) continue;

                    if(legal(state[j],state[k])) continue;
                    //第r-1行的士兵和第r-2行的士兵相互攻擊
                    if(legal(state[i],state[k])) continue;
                    //第r行的士兵和第r-1
                    dp[r][i][j]=max(dp[r][i][j] , dp[r-1][j][k]+soldier[i]);
                }
            }
        }

    int ans=0;
    for(int i=0; i<nums; i++)
        for(int j=0; j<nums; j++) //枚舉dp[row-1][i][j]
            ans=max(ans,dp[row-1][i][j]);
    printf("%d\n",ans);

    return 0;
}

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM