狀壓DP

狀態壓縮是設計dp狀態的一種方式。c++

當普通的dp狀態維數不少(或者說維數與輸入數據有關),但每一維總量不多時,能夠將多維狀態壓縮爲一維來記錄。spa

這種題目最明顯的特徵就是:都存在某一給定信息的範圍很是小(在20之內),而咱們在dp中所謂壓縮的就是這一信息。設計

(或者是在作題過程當中分析出了某一信息種類數不多)3d

咱們來看個例子。code

 

經典題blog

給出一個n*m的棋盤,要放上一些棋子,要求不能有任意兩個棋子相鄰。求方案數。it

n<=100;class

m<=8。二進制

 

若是m固定的話能夠設f[i][0/1][0/1]...[0/1]表示每一行每一列放不放im

若是不是固定的呢?

咱們發現後面的多個0/1能夠當作一個二進制數

那咱們不就能夠用數字代替後面的維數嗎?
f[i][s]->f[i+1][s’](s&s’==0)

 

你會發現這樣記錄很暴力,狀態數是與m相關的指數級的,但同時也就是由於m小咱們就確實能夠這麼作。

 

其實本質就是很暴力的記錄狀態,只不過利用了題目自己的特殊條件(這一維很小),使得咱們並不會所以複雜度太高。

同時也就是說,若是題目自己沒有這樣一個較小的信息,就不能應用狀態壓縮。

狀態壓縮dp確定是有一維是指數級的,這正是狀態壓縮的特色。

來看一道題:

P1896 [SCOI2005]互不侵犯

                  

 

  

 

這個題能夠狀壓DP的很明顯的標誌就是數據範圍

咱們設f[i][j][k]表示當前在第i行,這一行及以前總共放了j個國王,當前的狀態是k

那麼咱們只要枚舉行,而後再枚舉狀態轉移就能夠了

怎麼判斷互不侵犯?

用位運算就能夠了

注意最後答案不能光統計最後一行,由於不必定在最後一行才用完全部的國王

代碼:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,king;
ll f[10][100][2000];
int s[2000],num[2000];
int cnt;
ll ans;

inline void pre()
{
    int tot=(1<<n)-1;
    for(int i=0;i<=tot;i++)
    {
        if(!((i<<1)&i))
        {
            s[++cnt]=i;
            int k=i;
            while(k)
            {
                num[cnt]+=k%2;
                k/=2;
            }
        }
    }
}

inline void dp()
{
    for(int i=1;i<=cnt;i++)
    {
        if(num[i]<=king) f[1][num[i]][i]=1;
    }
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=cnt;j++)
        {
            for(int k=1;k<=cnt;k++)
            {
                if((!(s[k]&s[j]))&&(!((s[k]<<1)&s[j]))&&(!(s[k]&(s[j]<<1))))
                {
                    for(int use=1;use<=king-num[j];use++)
                    {
                        f[i][num[j]+use][j]+=f[i-1][use][k];
                    }
                }
            }
        }
    }
}

int main()
{
    scanf("%d%d",&n,&king);
    pre();
    dp();
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=cnt;j++)
        {
            ans+=f[i][king][j];
        }
    }
    cout<<ans;
}
相關文章
相關標籤/搜索