您的位置:首页 > 其它

BZOJ--1087[轮廓线DP 状压][SCOI2005]

2017-05-14 20:08 204 查看
Description

  在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上

左下右上右下八个方向上附近的各一个格子,共8个格子。

什么是轮廓线DP?轮廓线DP就是对于每一个状态,需要记录它的轮廓线上的信息才能完成状态的转移,那么记录它轮廓线上的信息有什么好处呢?有时,用轮廓线来定义状态,特别是对于要比用整行或是整列来定义状态要好得多,本题就是这样一道问题。

对于本题,如果定义f[i][j][k]表示第i行,状态为j,放了k个王的方案数,这样要完成转移,就需要枚举下一状态,也就是需要枚举下一行的状态,同时还需要判断这两个状态能否转移,也就是这两个状态放在相邻行会不会使王能够互吃,这样写起来不仅麻烦,而且复杂度也承受不住。

利用轮廓线DP,我们就可以很好的解决这一问题,定义f[i][j][k][p]表示第i行第j列以前的都处理完了(从上往下,从左往右处理),共放了k个King,轮廓线状态为p的方案数,其中轮廓线状态p为一个二进制数(将上面的轮廓线上面的部分接到下面的部分的后面):



我们先来研究如何转移到下一状态,如果白色的格子上放一个king,那么白色格子的四周都不能有king,即:

(!(get(p,n-j+2)))&&(!get(p,n-j+1))&&(!get(p,n-j))&&(!get(p,n-j-1))其中get(x,y)表示获取数x从右往左数第y位的数。这样,只要就相当于去掉白色格子左上角的格子后再将白色格子加在它原来所在的位置f[i][j+1][k+1][p|(1<<(n−j))]+=f[i][j][k][p]。

否则,白色格子中不放king,只要将白色格子左上角的格子不管是1是0都改成0即可,f[i][j+1][k][p&(~(1<<(n-j)))]+=f[i][j][k][p]。

最后,如果当前节点处于行末,所以下一状态就是下一行的第一个,如果放:f[i+1][1][k+1][(1<<n)|(p>>1)]+=f[i][n][k][p],如果不放f[i+1][1][k][p>>1]+=f[i][n][k][p];

最后,不要忘记滚一下。

我们发现,对于一些”窄”棋盘的问题,如果记录它的轮廓线,就能很快速的推出下一状态,从而实现状态数量的缩减,使代码更加高效。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 15
#define LL long long
using namespace std;
LL n,K,ans,f[2][maxn][maxn*maxn][1030];
int get(int x,int d){
return (x>>(d-1))&1;
}
int main(){
freopen("king.in","r",stdin);
freopen("king.out","w",stdout);
scanf("%lld%lld",&n,&K);
f[1][1][1][1<<n]=f[1][1][0][0]=1;
for(int i=1;i<=n;i++){
memset(f[1-(i&1)],0,sizeof(f[1-(i&1)]));
for(int j=1;j<n;j++)
for(int k=0;k<=K;k++)
for(int p=0;p<1<<(n+1);p++){
f[i&1][j+1][k][p&(~(1<<(n-j)))]+=f[i&1][j][k][p];
if((!(get(p,n-j+2)))&&(!get(p,n-j+1))&&(!get(p,n-j))&&(!get(p,n-j-1)))
f[i&1][j+1][k+1][p|(1<<(n-j))]+=f[i&1][j][k][p];
}
for(int k=0;k<=K;k++)
for(int p=0;p<1<<(n+1);p++){
f[1-(i&1)][1][k][p>>1]+=f[i&1]
[k][p];
if((!(get(p,n+1)))&&(!(get(p,n))))
f[1-(i&1)][1][k+1][(1<<n)|(p>>1)]+=f[i&1]
[k][p];
}
}
for(int i=0;i<1<<(n+1);i++)ans+=f[n&1]
[K][i];
printf("%lld",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: