1. 程式人生 > >狀態壓縮dp總結 長期更新

狀態壓縮dp總結 長期更新

狀壓dp本人做的題目真的不太多...至今還未理解到其中的精髓.所以以下的思路描述中有存在不當的地方希望能夠指出.另外,有些地方說的比較複雜,因為本弱雞

對這些東西不是很理解.....多寫點有助於理解吧.

思路:

首先,我們可以發現對於每一行的當前位置能不能放炮兵,只與他的上一行和上上一行

的炮兵位置有關係,所以要開一個三維陣列轉移關係.

0表示不放大炮,1表示放大炮,同樣的,先要滿足硬體條件,即有的地方不能放大炮,

後就是每一行中不能有兩個1的距離小於2(保證橫著不互相攻擊),這些要預先處理一下。然後就是

狀態表示和轉移的問題了,因為是和前兩行的狀態有關,所以要開個三維的陣列來表示狀態,當前行

的狀態可由前兩行的狀態轉移而來。即如果當前行的狀態符合前兩行的約束條件(不和前兩行的大炮

互相攻擊),則當前行的最大值就是上一個狀態的值加上當前狀態中1的個數(當前行放大炮的個數) 

【狀態表示】dp[i][j][k] 表示第i行為第j個狀態,第i-1為第k個狀態時的最大炮兵個數。 

【狀態轉移方程】 dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+num[j]);//num[j]為第j個狀態中的二進位制1的個數

【DP邊界條件】dp[1][i][0] =num[i] 狀態i能夠滿足第一行的硬體條件

(注意:這裡的i指的是第i個狀態,不是一個二進位制數,開一個數組儲存二進位制狀態) 

思考:

對於這種有地形限制的,標記一個點其他周圍都有影響的,一般都要預處理.將所有可能的狀態和地形的限制預處理出來,每次列舉第i個狀態的時候,要首先滿足地形的限制,在滿足各行各列之間的關係.

#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=111;
int dp[maxn][maxn][maxn];//dp[i][j][k] 第i行為第j個狀態,第i-1行為第k個狀態時的最大炮兵數 
int status[maxn],num[maxn]; //status[i] 當前第i個狀態的二進位制 kk(1放大炮 0 不放).  
//num[i]存放與status相對應的當前的第i個狀態的二進位制中有多少個1,即放了多少炮兵 
char s[maxn];
int mp[maxn];//存放地形的限制,1表示不能放大炮,0表示可以放大炮. 
int n,m;
int _count(int x)
{
    int __=0;
    while(x)
    {
        if(x&1)
            __++;
        x>>=1;
    }
    return __;
}
int main()
{
    while(~scanf("%d %d",&n,&m))
    {
        memset(dp,-1,sizeof(dp));
        memset(status,0,sizeof(status));
        memset(num,0,sizeof(num));
        memset(mp,0,sizeof(mp));
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s);
            for(int j=0;j<m;j++)
            {
                if(s[j]=='H')
                    mp[i]+=(1<<j);
            }
        }
        int cnt=0;
        for(int i=0;i<(1<<m);i++)
        {
            if(!(i&(i<<2))&&!(i&(i<<1)))//一行內的炮兵不能相互攻擊. 
            {
                num[cnt]=_count(i);
                status[cnt++]=i;
            }
        }
        for(int i=0;i<cnt;i++)
        {
            if(!(status[i]&mp[1]))
            dp[1][i][0]=num[i];//第一行賦初值.
        }
        for(int i=2;i<=n;i++)
        {
            for(int j=0;j<cnt;j++)//第i行的第j個狀態
              {  if(status[j]&mp[i]) //第i行與第i行的地形不衝突
                    continue;
	            for(int k=0;k<cnt;k++)//第i-1行的第k個狀態
	            {
	                if(status[k]&mp[i-1]) //狀態與地形不衝突 
	                    continue;
	                if(status[j]&status[k]) //第i行與第i-1行不互相攻擊 
	                continue;
	                for(int t=0;t<cnt;t++)//第i-2行的第t個狀態
	                {
	                    if(status[t]&mp[i-2]) //狀態與地形不衝突
	                    continue;
	                    if(status[t]&status[k]) //第i-1行與i-2不互相攻擊 
	                    continue;
	                    if(status[t]&status[j]) //第i行與第i-1行不互相攻擊.
	                    continue;
	                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+num[j]);
	                }
	            }
        	}
        }
        int ans=0;
        for(int i=0;i<cnt;i++)
            for(int j=0;j<cnt;j++)
            {
            
                ans=max(ans,dp[n][i][j]);
            }
            printf("%d\n",ans);
            
    }
    return 0;
}


思路:

這個題目和炮兵陣地那個題目很像很像,不過這個是要割出來2*2的.還是老規矩,我們先來確定一下需要幾維.由於是2*2,那麼每次割玻璃只會對下面一行有影響.也就是說當前行能否割,只與上一行的狀態有關了,所以二維就足夠了。

其次因為這是一個2*2的我們列舉1表示切方格的時候只能列舉四個中的一小塊,所以這裡我設當第i行第j位為1的時候,那麼他就按照這樣切,表示它和第i+1行的 j-1 j 四塊,構成一個2*2.

01

00

那麼我們最後只需要統計第n-1行的所有可能狀態中二進位制數最多的那個.另外我們發現它只對三個方向和自身地形,4個位置不能衝突.另外需要注意的就是按照我們這種的

切割方式它最前面永遠不可能是1只能是0.所以我們列舉狀態的時候只要列舉到(1<<(m-1))就可以了.

根據炮兵陣地的總結,存在這種地形限制問題的, 我們都需要進行預處理,即把所有可能的情況預處理出來,把自己地形的限制預處理出來,(能切割的地方為0,不能切割的地方為1).

最後給出狀態轉移方程:

dp[i][j]=max(dp[i][j],dp[i-1][k]+num[j])

//dp[i][j]表示第i行的第j個狀態.(是所有可行狀態中的第j個,並不代表一個二進位制數)

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1024;
int dp[maxn][maxn];
int status[maxn],num[maxn];
int mp[maxn];
int t,n,m;
int __(int x)
{
    int _=0;
    while(x)
    {
        if(x&1)
            _++;
        x>>=1;
    }
    return _;
}
int check_row(int r,int i)//判斷第r行的第i個狀態和r的地形是否衝突
{
    int sta=status[i];
    if(mp[r]&sta||mp[r+1]&sta||mp[r]&(sta<<1)||mp[r+1]&(sta<<1)) return 0;
    return 1;
}
int check_sta(int i,int j)//判斷第i個狀態和第j個狀態是否衝突。
{
    int s1=status[i],s2=status[j];
    if(s1&s2||s1&(s2<<1)||(s1<<1)&s2) return 0;
    return 1;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        memset(mp,0,sizeof(mp));
        memset(status,0,sizeof(status));
        memset(num,0,sizeof(num));
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            int x;
            for(int j=0;j<m;j++)
            {
                scanf("%d",&x);
                if(!x)
                    mp[i]+=(1<<j);
            }
        }
        int cnt=0;
        for(int i=0;i<(1<<(m-1));i++)
        {
            if(!(i&(i<<1)))
            {
                num[cnt]=__(i);
                status[cnt++]=i;
            }
        }
        for(int i=0;i<cnt;i++)
        {
            if(check_row(1,i))
            dp[1][i]=num[i];

        }
        for(int i=2;i<=n;i++)
        {
            for(int j=0;j<cnt;j++)//列舉第i行的狀態
            {
                if(!check_row(i,j))//判斷列舉的當前行地形和第j個狀態是否衝突
                    continue;
                for(int k=0;k<cnt;k++)//列舉第i-1行的狀態.
                {
                    if(!check_row(i-1,k))//判斷第i-1行的地形和當前k狀態是否衝突.
                    continue;
                    if(!check_sta(j,k))//判斷上一行的狀態k和當前行狀態j是否衝突
                    continue;
                    dp[i][j]=max(dp[i][j],dp[i-1][k]+num[j]);
                }
            }
        }
        int ans=0;
        for(int i=0;i<cnt;i++)
            ans=max(ans,dp[n-1][i]);
        printf("%d\n",ans);
    }
    return 0;
}