1. 程式人生 > >狀壓dp水題題解來一發

狀壓dp水題題解來一發

四道大水題的解題報告 前幾天老劉找我們聊過之後,要我最近看看狀壓dp,於是乎就刷了幾道,結果第一題用了深搜,第二題用了模擬,終於到綠題了才是正經dp,下面是四道水題的解題報告 yeah,DJ

洛谷P2915

題目大意

給一個長為n的序列,將序列排序,要求每相鄰兩個數的差值要超過給定值k,問有多少種序列符合要求

題解

用一個二進位制整數j的第k位(從右往左)表示序列中是否含有第k個數 e.g. 當j=10101010j=10101010時,序列中包含第2,4,6,82,4,6,8個數

用dp[i,j]表示以第i個數結尾,且當前取數情況為j的合法序列數量 很顯然,1010101010101010

010這種情況,一定可以由1010100010101000這一種情況轉移得到,那麼我們只要列舉每一種情況的每一位,如果這一位已經有數,那麼就列舉沒有數的每一位,看它是否滿足情況

for(int i=0;i<n;i++) dp[i][1<<i]=1;
int maxn=1<<n;
for(int i=0;i<maxn;i++)//列舉每一種情況
    for(int j=0;j<n;j++)//列舉每一位
    {
        if(i&(1<<j))//已經包含了
        {
            for(int l=0;l<n;l++)//列舉每一位
{ if(!(i&(1<<l))&&abs(s[j]-s[l])>k)//該位還沒被填上且滿足要求 dp[l][i|(1<<l)]+=dp[j][i];//轉移 } } }

洛谷P3052

題目大意

給出n個物品,體積為w[i],現把其分成若干組,要求每組總體積<=W,問最小分組。(n<=18)

題解

看題目資料範圍就很狀壓,用一個二進位制數i的第k位表示是否已經將第k個物品分好了組,f[i]表示第i種情況屬於哪一組,g[i]表示i這種情況下,i這一組剩下的體積。列舉每一個i,列舉i的每一位j,如果剩餘體積還能把第j’個物品放下,就轉移

    for(int i=0;i<(1<<n);i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i&(1<<(j-1))) continue;//這一位已經取了
            if(g[i]>=c[j]&&f[i|(1<<(j-1))]>=f[i])//放得下
            {
                f[i|(1<<(j-1))]=f[i];
                g[i|(1<<(j-1))]=max(g[i|(1<<(j-1))],g[i]-c[j]);
            }
            else if(g[i]<c[j]&&f[i|(1<<(j-1))]>f[i])//放不下
            {
                f[i|(1<<(j-1))]=f[i]+1;
                g[i|(1<<(j-1))]=max(g[i|(1<<(j-1))],w-c[j]);
            }
        }
    }

洛谷P2622

題目大意

n盞燈,m個按鈕,a[i,j]表示第i個按鈕對j盞燈的控制效果,若a[i,j]為1,那麼若燈開著,將其熄滅,若a[i,j]為-1,那麼若燈關著,將其開啟,若a[i,j]為0,則不管,剛開始燈全亮著及所有控制效果,求最少的按按鈕的次數。(n&lt;=10,m&lt;=100)(n&lt;=10,m&lt;=100)

題解

這個狀壓似乎就很顯然了,i的每一位表示這盞燈是否開著,列舉每一位轉移就行 dp[(1<<n)-1]=0,答案是dp[0],顯然要倒著往前推

    memset(dp,0x3f,sizeof(dp));
    int maxn=(1<<n)-1;
    dp[(1<<n)-1]=0;
    for(int i=maxn;i>=0;i--)
    {
        for(int j=1;j<=m;j++)
        {
            int now=i;
            for(int k=0;k<n;k++)
            {
                if((i&(1<<k))&&a[j][k+1]==1) now^=(1<<k);
                if((!(i&(1<<k)))&&a[j][k+1]==-1) now^=(1<<k);
            }
            dp[now]=min(dp[now],dp[i]+1);
        }
    }

洛谷P2622

題目大意

一塊mn的土地,每個11的方塊都有一個狀態,若狀態為1,則可以種草,為0則不行,不能有草地相鄰(有鄰邊),問有幾種方案m,n&lt;=12m,n&lt;=12

題解

這個明顯是個二維狀壓dp(吧),列舉每一排的狀態,這一排每一塊是否種草,然後只要這一行是合法的,並且和上一行也沒有草地相鄰,就可以將上一行的對應狀態的答案累加到這一行的這種狀態中。 最後累加最後一行的每一種狀態的答案

    for(int i=1;i<=m;i++)
    for(int j=1;j<=n;j++)
    dp[i]=(dp[i]<<1)+field[i][j];
    //field[i,j]表示這一塊土地是否能種草
    //dp[i]表示最多最多的合法狀態
    int maxn=1<<n;
    for(int i=0;i<maxn;i++)
        st[i]=(((i&(i<<1))==0) && ((i&(i>>1))==0));
    //這個是保證左右都沒有草地相鄰
    f[0][0]=1;
    for(int i=1;i<=m;i++)
    for(int j=0;j<maxn;j++)
    if(st[j]&&((j&dp[i])==j))
    //沒有左右相鄰的而且每一塊要種草的都是能種的
    for(int k=0;k<maxn;k++)
    if((k&j)==0) f[i][j]=(f[i][j]+f[i-1][k])%mod;
    //上下兩排不相鄰
    int ans=0;
    for(int i=0;i<maxn;i++)
    ans=(ans+f[m][i])%mod;

peace