狀壓dp水題題解來一發
四道大水題的解題報告
前幾天老劉找我們聊過之後,要我最近看看狀壓dp,於是乎就刷了幾道,結果第一題用了深搜,第二題用了模擬,終於到綠題了才是正經dp,下面是四道藍水題的解題報告
yeah,DJ
洛谷P2915
題目大意
給一個長為n的序列,將序列排序,要求每相鄰兩個數的差值要超過給定值k,問有多少種序列符合要求
題解
用一個二進位制整數j的第k位(從右往左)表示序列中是否含有第k個數 e.g. 當時,序列中包含第個數
用dp[i,j]表示以第i個數結尾,且當前取數情況為j的合法序列數量 很顯然,這種情況,一定可以由這一種情況轉移得到,那麼我們只要列舉每一種情況的每一位,如果這一位已經有數,那麼就列舉沒有數的每一位,看它是否滿足情況
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,則不管,剛開始燈全亮著及所有控制效果,求最少的按按鈕的次數。
題解
這個狀壓似乎就很顯然了,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則不行,不能有草地相鄰(有鄰邊),問有幾種方案
題解
這個明顯是個二維狀壓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