1. 程式人生 > >HDU 4539 鄭廠長系列故事——排兵佈陣 (狀壓思維題)*

HDU 4539 鄭廠長系列故事——排兵佈陣 (狀壓思維題)*

#include<bits/stdc++.h>
using namespace std;
#define debug puts("YES");
#define rep(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define read(x,y) scanf("%d%d",&x,&y)
#define ll long long
#define lrt int l,int r,int rt
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define root l,r,rt
const int  maxn =1e6+5;
const int mod=1e9+7;
const int ub=1e6;
ll powmod(ll x,ll y){ll t; for(t=1;y;y>>=1,x=x*x%mod) if(y&1) t=t*x%mod; return t;}
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
int n,m,x,row[110];///每一行的狀態
int s[1<<11],cnt[1<<11];///合法狀態和可以排布的士兵數量
int dp[110][220][220];
int getcnt(int x){int ret=0;while(x>0) x-=(x&(-x)),ret++;return ret;}
/*
中文題目。

狀態壓縮,
先找出狀態轉移方程,
dp[i][j][k]=max(dp[i-1][k][p]): 列舉p。
代表在第i行狀態為j,第i-1行狀態為k時的最大值。
因為我們可以看出當相距三行及以上時狀態是獨立的,
所以我們需要用DP暴力列舉當前狀態的前兩行。

那麼下面就是狀態壓縮和位運算技巧了。
首先為了判定方便要把0壓縮成1,因為我們答案要列舉的是
不含零的子集,所以把0壓成1就可以用與運算直接判斷是否符合要求。

我們對於每行先把符合要求的狀態壓出來,然後直接在符合要求的狀態中進行列舉,
符合的條件是相鄰間距不能有剛好為2的。
四層迴圈,後三層每一層都有條件,第二層是列舉當前row的1的子集即可,
第三層是i層與i-1層符合要求,就是不能有剛好錯位的。
第四層是要求不能有和i層狀態重複的,這很容易判得(不用麻煩的判定和i-1層的關係)
這也是狀態轉移的技巧吧。
*/

int main()
{
   while(~scanf("%d%d",&n,&m))
   {
        for(int i=0;i<n;i++)
        {
            row[i]=0;
            for(int j=0;j<m;j++)
            {
                scanf("%d",&x);
                if(!x) row[i]=(row[i]<<1)|1;///把狀態反過來這樣方便點
                else row[i]<<=1;
            }
        }

        int idx=0,ans=0;
        for(int i=0;i<(1<<m);i++)
        {
            if(i&(i<<2)) continue;
            s[idx]=i,cnt[idx++]=getcnt(i);
        }

        memset(dp,0,sizeof(dp));///初始化為0還是必要的
        for(int i=0;i<idx;i++)
        {
            if(s[i]&row[0]) continue;
            dp[0][i][0]=cnt[i];
        }///初始化DP狀態陣列

        for(int i=1;i<n;i++)
        {
            for(int j=0;j<idx;j++)
            {
                if(s[j]&row[i]) continue;///必須是完全符合的才行
                for(int k=0;k<idx;k++)
                {
                    if( (s[j]&(s[k]>>1)) || (s[j]&(s[k]<<1)) ) continue;///
                    for(int p=0;p<idx;p++)
                    {
                        if( s[j]&s[p] ) continue;///有一個相同位都不行
                        dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+cnt[j]);///狀態轉移方程
                    }
                }
            }
        }
        for(int i=0;i<idx;i++) for(int j=0;j<idx;j++) ans=max(ans,dp[n-1][i][j]);
        printf("%d\n",ans);
    }
    return 0;
}