1. 程式人生 > >狀壓dp SCOI 2005 互不侵犯

狀壓dp SCOI 2005 互不侵犯

題意:在 N × N N×N 的棋盤裡面放 K K 個國王,使他們互不攻擊,共有多少種擺放方案。國王能攻擊到它上下左右,以及左上左下右上右下八個方向上附近的各一個格子,共 8

8 個格子。

考慮如何狀壓,對於每一行,我們用一個長度為 n n 的二進位制串表示每一行的狀態,比如 1010 1010 表示第一個,第三個位置上有國王,而 1010

1010 對應的十進位制數為 10 10 ,我們將每一行的狀態都用二進位制表示然後狀壓成一個十進位制數。一共有 2 n
1 2^{n}-1
種狀態,因為最多的就是一行全為 1 1 的情況。

接下來設計狀態: d p [ i ] [ j ] [ k ] dp[i][j][k] 表示第 i i 行的狀態為 j j j j 即為狀壓後的十進位制數),前 i i 行一共使用了 k k 個國王。這裡定義 n u m [ i ] num[i] 表示一行狀態為 i i 時的國王個數 k k 為當前這一行的狀態 j j 為上一行的狀態, q q 為到前一行總共放的國王個數。所以: d p [ i ] [ k ] [ q + n u m [ k ] ] + = d p [ i 1 ] [ j ] [ q ] ; dp[i][k][q+num[k]]+=dp[i-1][j][q];

首先我們先預處理出對於一行內哪些狀態是合法的,如果狀態 i i 合法即記 b o o k [ i ] = 1 book[i]=1 ,對於每一種合法狀態,我們處理出合法這種狀態需要的國王個數 n u m [ i ] num[i] 。對於同一行,一個國王不能有左右相鄰的國王,所以對於狀態 i i ,我們讓 i i & ( i > > 1 ) i (i>>1),i & ( i < < 1 ) (i<<1) 如果二者都為 0 0 ,即為合法。對於一個合法狀態 i i ,我們對它進行二進位制分解,求出其中有多少個 1 1 ,即為 n u m [ i ] num[i] ,然後設出初始狀態 f [ 1 ] [ x ] [ n u m [ x ] ] = 1 f[1][x][num[x]]=1

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;
}

接下來我們分別列舉每一行 i i ,前一行的狀態 j j ,如果前一行的狀態合法,就繼續列舉當前行的所有狀態 k k ,對於當前行的所有狀態,我們首先判斷它是否合法,然後讓前一行的狀態分別& k ( k &lt; &lt; 1 ) ( k &gt; &gt; 1 ) k,(k&lt;&lt;1),(k&gt;&gt;1) ,如果均為 0 0 即視為合法,列舉到前一行總共放的國王個數根據方程進行累加轉移。

最後列舉最後一行的所有狀態累加一下答案即可。

#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;
}