狀壓DP

狀壓DP

咱都是用下標表示狀態。然而,如果一個狀態有20個下標,但只表示一個01值,難道要用20維數組嗎?

不成!

電腦裏數字都是二進制,即一個01串,就可以用一個數表示20位的狀態。這個狀態也可以是有20個元素的集合,所以《挑戰程序設計競賽》中叫它“針對集合的動態規劃”。

理論上,下標都是unsighed int 型,而unsighed int 型是32位的,也就是能表示32維的狀態。不過這樣佔內存很大,當32位都是1時,就會有232 =4294967296這麼大的數組,早炸了。

一般題目都是16位的。

對集合的一些操作

  • 空集 0
  • 單元素集 { i } 1<<i //從0開始標號
  • 全集 U (1<<n)-1 //有n個元素
  • 從集合S中取元素 i (s>>i)&1
  • 並集 ST s|t
  • 交集 ST s&t
  • 從集合S中加入元素 i s|(1<<i)
  • 從集合S中去除元素 i s&~(1<<i)
  • 枚舉全集U的子集 for(int s=1;s<(1<<n);s++)
  • 枚舉集合S的子集 for(int S0=S;S0;S0=(S0-1)&S)
  • 枚舉全集中大小爲k的子集

    //抄的p156
    int comp=(1<<k)-1;
    while(comb<(1<<n)){ 
    //comb就是子集,處理它
    
    int x=comb&-comb,y=comb+x;//lowbit(comb)?
    comb=((comb&~y)/(x>>1))|y;
    }
    //一臉懵逼……
    

(來自《挑戰程序設計競賽》p156和藍書p70)

幾道題

經典的

UVa 10817 Headmaster’s Headache
——題意來自藍書p95——
某校有n個教師和m個求職者,已知每人的工資和能教的課程集合,要求支付最少的工資使得每門都至少有兩名教師教學,在職教師必須招聘。

輸入包含多組數據,每組第一行爲3個整數s,m,n,科目個數1<=s<=8,在職教師數1<=m<=20,求職者數1<=n<=100;以下m行每行描述一個在職教師,其中第一個整數c (10 000<=c<=50 000)是工資,接下來的若干整數是他的科目列表;接下來n行是申請者,格式相同,結束標誌是s=0。

UVa的輸入比較坑……
——下面是我瞎想的——
s不是1~8麼,而且是兩名教師,就可以把它“翻倍”,低8位是第一個教本科目的,高8位是第二個教本科目的,有一個就把低8位對應的變1,有兩個就把高8位對應的變1,然後就是01揹包啦~
還可以改爲低s位高s位(大數據相當於沒改),當高位有了時把低位置0。不過比較麻煩,其實內存還是可以接受的((2161)20=1310720 )。

瞎搞的

洛谷 P2704 炮兵陣地
這一題不是很經典,但也與狀壓有關

他把一行壓成一個數!
他把一行壓成一個數!
他把一行壓成一個數!

然後,這有兩個騷操作,其一是移一位再與,判斷有無相鄰兩個1,同理移兩位,判隔一個的。自己與自己,就是橫行;自己與上面,就是豎列。其二是篩選可能的方案數。其實這類方法很常見的,就是重複使用的東西先預處理出來,隨時取用。
O(n23m )(可能並不準)

P2704代碼

//p2704
#include<cstdio>
#include<iostream>
using namespace std;
const int N=105;
int mp[N],f[N][N][N],met[N],cnt[N],mcnt;
//f[i][j][k]:第i行,第i行方案j,第i-1行方案k,存最大放炮個數
//mp[i]:第i行的圖;met[i]:可能的方案method,由地圖決定
//cnt[i]:這個方案能放多少炮;mcnt:可能的方案個數
int main(){
    int n,m;
    char tmp[N];
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){  
        scanf("%s",tmp);
        for(int j=0;j<m;j++){
            mp[i]<<=1;
            if(tmp[j]=='H')
                mp[i]|=1;
            }
        }//把一橫行壓在一位裏,山丘==1
    for(int i=0;i<(1<<m);i++)
        if( (!(i&(i<<1)))&&(!(i&(i<<2))) ){
            met[++mcnt]=i;
            int tm=i;
            while(tm){
                cnt[mcnt]+=tm&1;
                tm>>=1;
                }
            }//選取所有方案,符合兩個1之間至少兩個0

    for(int i=1;i<=mcnt;i++){
        if(met[i]&mp[1])continue;
        f[1][i][1]=cnt[i];
        }//第一行方案初始化,第三維其實隨便
    for(int i=1;i<=mcnt;i++){//第一行的方案
        if(met[i]&mp[1])continue;//方案i與地圖衝突
    for(int j=1;j<=mcnt;j++){//第二行的方案
        if(met[j]&mp[2]||//方案j與地圖衝突
           met[j]&met[i])continue;//方案j與方案i衝突
        f[2][j][i]=cnt[j]+cnt[i];//方案i與j的數目總和
        }
    }

    for(int i=3;i<=n;i++){//第i行
        for(int j=1;j<=mcnt;j++){//第i行方案j
            if(met[j]&mp[i])continue;//方案i與地圖衝突
            for(int k=1;k<=mcnt;k++){//第i-1行方案k
                if(met[k]&mp[i-1]//方案k與地圖衝突
                 ||met[k]&met[j])continue;//方案k與方案j衝突
                for(int l=1;l<=mcnt;l++){//第i-2行方案l
                   if(met[l]&mp[i-2]//l與地圖衝突
                    ||met[l]&met[j]//l與j衝突
                    ||met[l]&met[k])continue;//l與k衝突                 
                   f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+cnt[j]);
                    }
                }
            }
        }
    int ans=0;
    for(int i=1;i<=mcnt;i++)
        for(int j=1;j<=mcnt;j++)
            ans=max(ans,f[n][i][j]);
    printf("%d",ans);

    return 0;
    }

打完已廢……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章