D 1087: [SCOI2005]互不侵犯King (状态压缩dp)

50 篇文章 0 订阅
21 篇文章 0 订阅

Description

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

Input

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

Output

方案数。

Sample Input

3 2

Sample Output

16

题解:

一开始以为是水搜索,结果和n皇后完全不是一码事啊!!!

额,状态压缩dp吧;

首先,因为是状压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)来转移了!


code(无耻地拿了别人的):

  1. #include<cstdio>  
  2. #define STEP 512  
  3. using namespace std;  
  4. long long f[10][101][STEP],ans;  
  5. int m,n,num,i,j,now,k,stay[101],cnt[101]; //stay记录每种状态压缩后的值,cnt记录对应的状态中1的个数。  
  6. bool map[101][101];  
  7. void dfs(int k,int put,int now)  //预处理,k是放了几颗,put是当前放的位置,now是当前状态压缩后的值。  
  8. {  
  9.   stay[++m]=now;cnt[m]=k;  
  10.   if (k>=(n+1)/2||k>=num) return;  
  11.   for (int i=put+2;i<=n;i++)  
  12.     dfs(k+1,i,now+(1<<(i-1)));  
  13. }  
  14. void init()  
  15. {  
  16.   dfs(0,-1,0);                 //预处理出每种状态,共有m种。  
  17.   for (int i=1;i<=m;i++)  
  18.     for (int j=1;j<=m;j++) //<span style="font-family: Arial, Helvetica, sans-serif;">枚举某两种状态是否能相邻。一共有三种不能的情况:上下都是1,左上、右下是1,左下、右上是1。 </span>  
  19.       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;    //边界  
  20. }  
  21. int main()  
  22. {  
  23.   scanf("%d%d",&n,&num);  
  24.   init();  
  25.   for (i=2;i<=n;i++)  
  26.   {  
  27.     for (j=0;j<=num;j++)  
  28.     {  
  29.       for (now=1;now<=m;now++)  
  30.       {  
  31.         if (cnt[now]>j) continue;  
  32.         for (k=1;k<=m;k++)  
  33.           if (map[k][now]&&cnt[k]+cnt[now]<=j) f[i][j][now]+=f[i-1][j-cnt[now]][k]; //转移  
  34.       }  
  35.     }  
  36.   }  
  37.   for (i=1;i<=m;i++)  
  38.     ans+=f[n][num][i];  
  39.   printf("%I64d",ans);  
  40.   return 0;  
  41. }  

自己的(差不多):

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
long long f[10][101][1024],ans;
int stay[101],cnt[101],num,n,m;
bool _is[1024][1024];
void dfs(int k,int put,int 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)));
}
int main(){
   cin>>n>>num;
    dfs(0,-1,0);
    for(int i=1;i<=m;i++)
     for(int j=1;j<=m;j++){
        if((stay[i]&stay[j])||((stay[i]<<1)&stay[j])||((stay[i]>>1)&stay[j]))_is[i][j]=false;
        else _is[i][j]=true;
     }
      for (int i=1;i<=m;i++)f[1][cnt[i]][i]=1ll;
    for(int i=2;i<=n;i++)
    for(int j=0;j<=num;j++)
    for(int now=1;now<=m;now++)
    {
       if(cnt[now]>j)continue;
       for(int k=1;k<=m;k++)
       if(_is[k][now]&&cnt[now]+cnt[k]<=j)f[i][j][now]+=f[i-1][j-cnt[now]][k];


    }
    for(int i=1;i<=m;i++)
      ans+=f[n][num][i];
cout<<ans;


 return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值