【问题描述】在n*n(1<=n<=10)的棋盘上放k(0<=k<=n*n)个国王(可攻击相邻的8 个格子),求使它们没法互相攻击的方案总数。算法
【输入格式】输入有多组方案,每组数据只有一行为两个整数n和k。数组
【输出格式】每组数据一行为方案总数,若不可以放置则输出0。数据结构
【问题分析】spa
由问题很容易联想起经典的“八皇后”问题,彷佛就是“皇后”变成了“国王”,并且格子范围彷佛也差很少,所求问题也同样。那么这个问题也能用搜索解决吗?.net
可稍加分析可知搜索是很难胜任的,由于国王的数目能够是很大,加上它与“八皇后”问题的一个本质上的不一样即是每一个国王只影响周围的一个格子,因此剪枝条件也不多,指数级别的搜索是没法在时限内出解的。code
那么通常的动态规划能解决吗?典型的二维DP,F[I,J]彷佛没法很好地把状态表示出来,所以咱们只能考虑状态压缩的动态规划。blog
首先咱们要注意到这题的关键——每一个国王只影响周围八个方向的一个格子,它虽然否认了搜索,却给状态压缩带来了无限生机!get
咱们改变以前动态规划的思惟方式,一行一行地摆放国王,当咱们摆放第I行时,这一行只会和先后一行的互相影响,而这一行的状态是能够由咱们肯定的。那是否能够把一行看成一个总体,而后像传统的动态规划那样进行处理呢?让咱们试一下。string
每一行它对下一行的影响就体如今这一行的摆放方式以及以前总共放了多少个国王。因此咱们能够把摆放方式做为状态,设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; }