DP - 狀壓DP - SGU - 223 - Little Kings
在 n×n 的棋盤上放 k 個國王,國王可攻擊相鄰的 8 個格子,求使它們無法互相攻擊的方案總數。
輸入格式
共一行,包含兩個整數 n 和 k。
輸出格式
共一行,表示方案總數,若不能夠放置則輸出0。
數據範圍
1≤n≤10,
0≤k≤n2
輸入樣例:
3 2
輸出樣例:
16
分析:
本題與——《蒙德里安的夢想》相似,只是擺放的物品佔據的範圍不同。
經過分析,容易發現:由於每個國王僅佔據相鄰的八個方格,故每一行擺放的方案僅與上一行的狀態有關。
那麼我們可以通過一個二進制數s來表示第i行的狀態,1表示該位置放一個國王,0表示不放。由於國王的數量有限制,因此我們的狀態表示需要三維。
狀態表示,f[i][j][s]:已經擺放了前i行,且用了j個物品,且第i行的狀態是s的總方案數。
狀態計算:假設第i行的狀態是a,第i−1行的狀態是b,若a能夠由b轉移而來,需要滿足:①、第i−1行本身是合法的:二進制數b不能存在連續的1。
②、狀態a和狀態b不能存在衝突:Ⅰ、第i行放國王的位置的正上方不能有國王,需滿足a&b=0。
Ⅱ、第i行放國王的位置的斜上方不能有國王,需滿足a∣b表示的二進制數不能有連續的1。這一條件同時也避免了第i行出現連續的1。
若滿足以上兩個條件,轉移方程:f[i][j][a]+=f[i−1][j−count(a)][b],其中count(a)表示狀態a中1的個數,即第i行擺放國王的數量。
具體落實:
①、先預處理每一行的所有合法狀態(不能含有連續的1),同時記錄下這些狀態當中1的個數(狀態轉移的時候要用)。
②、將所有互不影響的狀態預處理出來,用head數組做一個映射。
③、計算每個狀態的方案總數。
注意:
①、初始化邊界。對每一層而言,擺放的國王數量爲0時也是一種合法方案,因此國王數量j從0開始循環。
②、最後要輸出第n層的方案總數之和,即∑f[n][m][s],我們可以在計算時算到n+1層,最後輸出f[n+1][m][0],這是等價的。因爲狀態爲0可由前一層的所有合法狀態轉移而來,相當於在計算方案數的過程中就做了一遍求和。
代碼:
#include<iostream>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;
const int N=12,M=1<<10,K=110;
int n,m;
ll f[N][K][M];
int cnt[M];
vector<int> state;
vector<int> head[M];
bool check(int x)
{
for(int i=0;i<n-1;i++)
if((x>>i)&1 && (x>>i+1)&1)
return false;
return true;
}
int Count(int x)
{
int res=0;
for(int i=0;i<n;i++) res+=(x>>i)&1;
return res;
}
int main()
{
cin>>n>>m;
for(int i=0;i<1<<n;i++)
if(check(i))
{
state.push_back(i);
cnt[i]=Count(i);
}
int len=state.size();
for(int i=0;i<len;i++)
for(int j=0;j<len;j++)
{
int a=state[i],b=state[j];
if((a&b)==0 && check(a|b))
head[i].push_back(j);
}
f[0][0][0]=1;
for(int i=1;i<=n+1;i++)
for(int j=0;j<=m;j++)
for(int a=0;a<len;a++)
for(int b:head[a])
{
int c=cnt[state[a]];
if(j>=c) f[i][j][state[a]]+=f[i-1][j-c][state[b]];
}
cout<<f[n+1][m][0]<<endl;
return 0;
}