狀壓dp SCOI 2005 互不侵犯
題意:在 的棋盤裡面放 個國王,使他們互不攻擊,共有多少種擺放方案。國王能攻擊到它上下左右,以及左上左下右上右下八個方向上附近的各一個格子,共 個格子。
考慮如何狀壓,對於每一行,我們用一個長度為 的二進位制串表示每一行的狀態,比如 表示第一個,第三個位置上有國王,而 對應的十進位制數為 ,我們將每一行的狀態都用二進位制表示然後狀壓成一個十進位制數。一共有 種狀態,因為最多的就是一行全為 的情況。
接下來設計狀態: 表示第 行的狀態為 ( 即為狀壓後的十進位制數),前 行一共使用了 個國王。這裡定義 表示一行狀態為 時的國王個數, 為當前這一行的狀態, 為上一行的狀態, 為到前一行總共放的國王個數。所以:
首先我們先預處理出對於一行內哪些狀態是合法的,如果狀態 合法即記 ,對於每一種合法狀態,我們處理出合法這種狀態需要的國王個數 。對於同一行,一個國王不能有左右相鄰的國王,所以對於狀態 ,我們讓 & & 如果二者都為 ,即為合法。對於一個合法狀態 ,我們對它進行二進位制分解,求出其中有多少個 ,即為 ,然後設出初始狀態 。
for(ll i=0;i<(1<<n);++i)
{
if(!(i&(i>>1))&&!(i&(i<<1)))
book[i]=1;
ll w=i;
while(w)
{
if(w%2==1)
num[i]++;
w/=2;
}
if(book[i])
dp[1][i][num[i]]=1;
}
接下來我們分別列舉每一行 ,前一行的狀態 ,如果前一行的狀態合法,就繼續列舉當前行的所有狀態 ,對於當前行的所有狀態,我們首先判斷它是否合法,然後讓前一行的狀態分別& ,如果均為 即視為合法,列舉到前一行總共放的國王個數根據方程進行累加轉移。
最後列舉最後一行的所有狀態累加一下答案即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll dp[20][1000][100],num[1000];
bool book[1000];
ll n,king,ans;
int main()
{
cin>>n>>king;
for(ll i=0;i<(1<<n);++i)
{
if(!(i&(i>>1))&&!(i&(i<<1)))
book[i]=1;
ll w=i;
while(w)
{
if(w%2==1)
num[i]++;
w/=2;
}
if(book[i])
dp[1][i][num[i]]=1;
}
for(ll i=2;i<=n;++i)
for(ll j=0;j<(1<<n);++j)
if(book[j])
for(ll k=0;k<(1<<n);++k)
if(book[k]&&!(k&j)&&!((k<<1)&j)&&!((k>>1)&j))
for(ll q=num[j];q+num[k]<=king;++q)
dp[i][k][q+num[k]]+=dp[i-1][j][q];
for(ll i=0;i<(1<<n);++i)
ans+=dp[n][i][king];
cout<<ans;
return 0;
}