1. 程式人生 > >SGU 223 little kings BSOJ2772 狀壓DP

SGU 223 little kings BSOJ2772 狀壓DP

而且 進制 print 剪枝 描述 計算機 tex 範圍 blog

1896 [SCOI2005]互不侵犯King

【問題描述】在n*n(1<=n<=10)的棋盤上放k(0<=k<=n*n)個國王(可攻擊相鄰的8 個格子),求使它們無法互相攻擊的方案總數。

【輸入格式】輸入有多組方案,每組數據只有一行為兩個整數n和k。

【輸出格式】每組數據一行為方案總數,若不能夠放置則輸出0。

【問題分析】

由問題很容易聯想起經典的“八皇後”問題,似乎就是“皇後”變成了“國王”,而且格子範圍似乎也差不多,所求問題也一樣。那麽這個問題也能用搜索解決嗎?

可稍加分析可知搜索是很難勝任的,因為國王的數目可以是很大,加上它與“八皇後”問題的一個本質上的不同便是每個國王只影響周圍的一個格子

,所以剪枝條件也很少,指數級別的搜索是無法在時限內出解的。

那麽一般的動態規劃能解決嗎?典型的二維DP,F[I,J]似乎無法很好地把狀態表示出來,因此我們只能考慮狀態壓縮的動態規劃。

首先我們要註意到這題的關鍵——每個國王只影響周圍八個方向的一個格子,它雖然否定了搜索,卻給狀態壓縮帶來了無限生機!

我們改變之前動態規劃的思維方式,一行一行地擺放國王,當我們擺放第I行時,這一行只會和前後一行的互相影響,而這一行的狀態是可以由我們確定的。那是否可以把一行當作一個整體,然後像傳統的動態規劃那樣進行處理呢?讓我們試一下。

每一行它對下一行的影響就體現在這一行的擺放方式以及之前總共放了多少個國王。所以我們可以把擺放方式

作為狀態,設f[i,j,s]表示第i行狀態為a[j]且前i行已放s個國王的方案總數。這樣很容易便得到了一個粗略的方程:

F[i,j,S]=∑F[i-1,k,T]

a[j],a[k]分別表示一種擺放方式,F[i,j]表示第i行用a[j]的擺放方式,且a[j]與a[k]相兼容。並且S等於T加上a[j]這種方式在這一行放置的國王數。

很明顯這個方程是沒有後效性的,可關鍵就在於j,k怎麽在計算機上表示出來,這就需要我們的主題:狀態壓縮

看圖便知,每一個格子只有兩種狀態,放和不放,並且註意到格子寬度最大為9。由這便想到了熟悉的二進制表示法。每一個格子對應一個二進制位。這樣每一行便對應一個N位的二進制數,如下圖所示:

1 0 0 0 1 0 0 0

即十進制的128+8=136

這樣我們就可以把一種擺放方式轉化為一個數,這樣上面方程中的J,K就可以用數字來代替。我們就把一行的擺放方式作為狀態,並把它壓縮成了一個數!具體的算法流程如下:

①對於每一行,我們通過搜索得出一個合法狀態。

②然後再枚舉上一行與這一行相容的狀態再累加即可,狀態用N位的二進制數表示,最大僅為512,所以一個512*9*81的數組就可以了,還可以用滾動數組的技巧。空間是肯定可以承受的。

而一個粗略的時間復雜度:O(K*N*2^N*2^N),似乎大了點。不過註意到由於國王是不能相鄰放置的。所以我們可以用一個f(n)來表示當列數為n時每一行可能的放置總數。則f(n)=f(n-1)+f(n-2)初始值:f(1)=2,f(2)=3。

則f(9)=89。因此最大也才是9*89*89*81≈6000000。是可以承受的。

壓縮行,有king是1,沒有是0.
判斷可行,就是將上一行與這一行按位與,接著左移,右移。
剩下的就是簡單的dp了。

#include<iostream>  
#include<cstring>  
#include<cstdio>  
using namespace std;  
const int N=10;  
int n,k,a[10005],b[10005],tp;  
long long f[N][N*N][1<<N],ans;  
//第i行,一共擺放了j個king,第i行的擺放情況是g   
void dfs(int num,int lst,int at,int bt)  
{  
    if(num==n)  
    {  
        ++tp;  
        a[tp]=at;  
        b[tp]=bt;  
        return ;  
    }  
    dfs(num+1,lst,at,bt);  
    if(num-lst>=2)  
    {  
        at|=(1<<num);  
        bt++;  
        dfs(num+1,num,at,bt);  
    }  
}  
int main()  
{  
    scanf("%d%d",&n,&k);  
    dfs(0,-2,0,0);  
    f[0][0][0]=1;  
    int end=(1<<n)-1;  
    for(int i=1;i<=n;i++)  
        for(int j=0;j<=k;j++)  
            for(int g=0;g<=end;g++)  
                if(f[i-1][j][g]>0)  
                    for(int h=1;h<=tp;h++)  
                        if((a[h]&g)==0&&(a[h]&(g<<1))==0&&(a[h]&(g>>1))==0&&j+b[h]<=k)  
                            f[i][j+b[h]][a[h]]+=f[i-1][j][g];  
    for(int i=0;i<=end;i++)  
        ans+=f[n][k][i];  
    printf("%lld\n",ans);  
    return 0;  
}  

SGU 223 little kings BSOJ2772 狀壓DP