[分析]
參考:http://blog.sina.com.cn/s/blog_8442ec3b0100xgpb.html
由於狀態比較多,首先預處理出只考慮這一行滿足要求的狀態,並求出c[i]表示這個狀態含1的個數。
設f[i][j][k]表示前i行,共放了j個,第i行的放置情況爲k的方案數。
初始狀態:f[0][0][(00...0)]=1。
動態轉移方程:f[i][j][k]=sum{f[i-1][j-c[k]][l]},legal(l,k)=true。這裏的legal(l,k)即檢查相鄰兩行有沒有問題。
所求答案:sum{f[n][kk][l]}。
[小結]這種要滿足包括了當前行和之前行的狀壓dp,先進行預處理減少狀態。
[代碼]
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
typedef long long LL;
const int N=10;
const int K=100;
int n,kk;
int p[1<<N],c[1<<N];
LL f[N][K][1<<N],res;
inline void pre(int now)
{
for (int i=1;i<=n-1;i++)
if (now&1<<i-1&&now&1<<i) return;
p[++p[0]]=now;
for (int i=1;i<=n;i++) c[p[0]]+=(now&1<<i-1)>0;
}
inline int check(int k,int l)
{
for (int i=1;i<=n;i++)
if (k&1<<i-1)
{
if (l&1<<i-1) return 0;
if (i^1&&l&1<<i-2) return 0;
if (i^n&&l&1<<i) return 0;
}
return 1;
}
int main(void)
{
scanf("%d%d",&n,&kk);
for (int i=0;i<1<<n;i++) pre(i);
f[0][0][1]=1;
for (int i=1;i<=n;i++)
for (int k=1;k<=p[0];k++)
for (int j=c[k];j<=kk;j++)
for (int l=1;l<=p[0];l++)
if (check(p[k],p[l]))
f[i][j][k]+=f[i-1][j-c[k]][l];
for (int i=1;i<=p[0];i++) res+=f[n][kk][i];
printf("%lld\n",res);
return 0;
}