bzoj 1087 [SCOI2005] 互不侵犯King 题解

转载请注明:http://blog.csdn.net/jiangshibiao/article/details/23732795

【原题】

1087: [SCOI2005]互不侵犯King

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 1326   Solved: 767
[ Submit][ Status]

Description

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

Input

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

Output

方案数。

Sample Input

3 2

Sample Output

16

【分析】一开始以为是深搜~~记得USACO里的八皇后n=13呢,这里才9.但是很快我就发现这是不明智的。经各路大神指点:这是状态压缩的DP。

【过程】翻遍了网上的题解,但是大多都过于简略,像我这种蒟蒻根本看不懂。于是开始自己YY。

首先,因为是状压DP,方程的某一维一定是一个状态。n<=9,我们可以用01串表示某一行放置的情况。2^0~2^8,那么总共状态就是2^9-1。空间还是承受的起。当然还要枚举一下当前做到第几行,以及当前一共放了几颗棋子。很快,有关f的表示就出来了:用f[i][j][now]表示到第i行,一共放j个棋子(包括这之前的),且第i行的状态是k的方案数。再考虑转移。这一行肯定是由上一行的状态转移过来的,那么我们可以再枚举上一行的状态。

很自然的,发现这会超时。每次枚举一种状态就需要2^9,两重循环已经快爆掉了!我们可以发现一件事情。比如n=5,我们每次枚举到的11111,11011,10111,01011这些状态都是无效的!那么我们可以先预处理一下对于每一行的所有可行的状态(就是不能有连续的1)。这样的效率仍然不高——我们还可以对于每种可行的状态i,j,预处理i和j是否能够相邻,这样我们在DP的时候,就可以O(1)来转移了!

【代码】

#include<cstdio>
#define STEP 512
using namespace std;
long long f[10][101][STEP],ans;
int m,n,num,i,j,now,k,stay[101],cnt[101]; //stay记录每种状态压缩后的值,cnt记录对应的状态中1的个数。
bool map[101][101];
void dfs(int k,int put,int now)  //预处理,k是放了几颗,put是当前放的位置,now是当前状态压缩后的值。
{
  stay[++m]=now;cnt[m]=k;
  if (k>=(n+1)/2||k>=num) return;
  for (int i=put+2;i<=n;i++)
    dfs(k+1,i,now+(1<<(i-1)));
}
void init()
{
  dfs(0,-1,0);                 //预处理出每种状态,共有m种。
  for (int i=1;i<=m;i++)
    for (int j=1;j<=m;j++) //枚举某两种状态是否能相邻。一共有三种不能的情况:上下都是1,左上、右下是1,左下、右上是1。 
      map[i][j]=map[j][i]=((stay[i]&stay[j])||((stay[i]>>1)&stay[j])||((stay[i]<<1)&stay[j]))?0:1;                                                          for (int i=1;i<=m;i++)f[1][cnt[i]][i]=1ll;    //边界
}
int main()
{
  scanf("%d%d",&n,&num);
  init();
  for (i=2;i<=n;i++)
  {
    for (j=0;j<=num;j++)
    {
      for (now=1;now<=m;now++)
      {
        if (cnt[now]>j) continue;
        for (k=1;k<=m;k++)
          if (map[k][now]&&cnt[k]+cnt[now]<=j) f[i][j][now]+=f[i-1][j-cnt[now]][k]; //转移
      }
    }
  }
  for (i=1;i<=m;i++)
    ans+=f[n][num][i];
  printf("%I64d",ans);
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值