【状压DP】bzoj1087 互不侵犯king

一、题目

Description

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

Input

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

Output

方案数

Sample Input

3 2

Sample Output

16

原题链接→_→bzoj1087: [SCOI2005]互不侵犯King

二、题目分析

其实我们可以用一张美妙的表来解决这道题(划掉)这道题我们首先考虑暴搜解决,然而似乎状态略多会炸……

搜索不行,我们很容易能想到DP,这里我们引入一个神奇的DP——状态压缩型动态规划。状态压缩是状压DP的核心(废话!),本人在上一篇blog中详细介绍了状态压缩的思想,诸位看官不妨移步一看,可能会对理解状压DP起到一定帮助(链接→_→【宽度优先搜索】神奇的状态压缩 CodeVs1004四子连棋

对于每一行的状态,我们用1表示国王,0表示不放国王。由于每个位置只有0和1两种状态,我们可以使用位运算判断每行的状态是否合法。例如:10101010就是一种合法状态,而11111111就不合法。每行状态表示的十进制数就是我们压缩后的状态。

我们需要做两个预处理,先枚举每行所有可能的状态,记录下合法状态。然后判断两个状态是否能作为临行放置,并用布尔类型的二维数组存储其关系。

预处理之后,就是常规的DP,状态转移方程如下:

f[i][j][now]=∑f[i-1][j-num[now]][q]

略作说明:f[i][j][k]代表前i行,总共放j个国王,第i行状态为k时的方案数。状态转移方程中的num[now]代表状态为now的行中国王的数目,q表示第i-1行的状态。

三、代码实现

 1 #include<stdio.h>
 2 int n,k;
 3 long long ans;
 4 bool s[600],map[600][600];//判断状态是否合法;判断两个状态是否能作为临行 
 5 int num[600];//num[i]代表编号为i的状态含有的国王数 
 6 long long f[10][100][600];
 7 int sum;
 8 void pre_s()//预处理s数组 
 9 {
10     int i; 
11     for(i=0;i<sum;++i)
12     {
13         if((i&(i<<1))==0)
14         {
15             s[i]=true;
16             int t=i,cnt=0;
17             while(t)
18             {
19                 cnt+=(t&1);
20                 t>>=1;
21             }
22             num[i]=cnt;
23             f[1][cnt][i]=1;
24         }
25     }
26 }
27 void pre_map()//预处理map数组 
28 {
29     int i,j;
30     for(i=0;i<sum;++i)
31     {
32         if(!s[i])continue;
33         for(j=0;j<sum;++j)
34         {
35             if(!s[j])continue;
36             if((!(i&j))&&(!((i<<1)&j))&&(!((i>>1)&j)))map[i][j]=true;
37         }
38     }
39 }
40 void dp()
41 {
42     int i,j,now;
43     for(i=2;i<=n;++i)
44         for(j=0;j<=k;++j)
45             for(now=0;now<sum;++now)
46             {
47                 if(!s[now])continue;
48                 if(num[now]>j)continue;
49                 int q;
50                 for(q=0;q<sum;q++)
51                 {
52                     if(!s[q])continue;
53                     if(!map[now][q])continue;
54                     if(num[q]+num[now]>j)continue;
55                     f[i][j][now]+=f[i-1][j-num[now]][q];
56                 }
57             }
58 }
59 int main()
60 {
61     scanf("%d%d",&n,&k);
62     sum=1<<n;
63     pre_s();
64     pre_map();
65     dp();
66     for(int i=0;i<sum;++i)
67     {
68         if(!s[i])continue;
69         ans+=f[n][k][i];
70     }
71     printf("%lld",ans);
72     return 0;
73 } 
bzoj1087 互不侵犯king

弱弱地说一句,本蒟蒻码字也不容易,转载请注明出处http://www.cnblogs.com/Maki-Nishikino/p/5992703.html

转载于:https://www.cnblogs.com/Maki-Nishikino/p/5992703.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值