互不侵犯
题目链接
emmm
题干
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
输入样例
3 2
输出样例
16
数据范围
1 ≤ N ≤ 9 , 0 ≤ K ≤ N 2 1 \leq N \leq 9, 0 \leq K \leq N^2 1≤N≤9,0≤K≤N2
解法一、
知识点
- 位运算
- dp
3.状压dp=(位运算+dp)
解法概括
首先我们考虑只有一列的情况
我们可以设 f ( i , j , k ) f(i,j,k) f(i,j,k)
- i i i:第 i i i行
- j j j:0/1,表示这一行是否有国王
- k k k:已经放置的国王数
那么显然我们的状态转移方程为
f
[
i
]
[
1
]
[
k
]
=
f
[
i
−
1
]
[
0
]
[
k
]
f[i][1][k]=f[i-1][0][k]
f[i][1][k]=f[i−1][0][k]
f
[
i
]
[
0
]
[
k
]
=
f
[
i
−
1
]
[
1
]
[
k
]
f[i][0][k]=f[i-1][1][k]
f[i][0][k]=f[i−1][1][k]
边界条件为
f
[
1
]
[
1
]
[
1
]
=
f
[
1
]
[
0
]
[
0
]
=
1
f[1][1][1]=f[1][0][0]=1
f[1][1][1]=f[1][0][0]=1
最后统计
a
n
s
=
f
[
n
]
[
1
]
[
K
]
+
f
[
n
]
[
0
]
[
K
]
=
2
(
→
_
→
)
ans=f[n][1][K]+f[n][0][K]=2 (→\_→)
ans=f[n][1][K]+f[n][0][K]=2(→_→)
其次我们再考虑两列的情况
我们需要设 f ( i , j 1 , j 2 , k ) f(i,j_1,j_2,k) f(i,j1,j2,k)
状态转移方程这里就不在累述,显而易见,我们的状态表示数组变成了四维的,推而广之
- 三列: f ( i , j 1 , j 2 , j 3 , k ) f(i,j_1,j_2,j_3,k) f(i,j1,j2,j3,k)
- 四列: f ( i , j 1 , j 2 , j 3 , j 4 , k ) f(i,j_1,j_2,j_3,j_4,k) f(i,j1,j2,j3,j4,k)
- n列: f ( i , j 1 , j 2 , j 3 , ⋯   , j n , k ) f(i,j_1,j_2,j_3 ,\cdots,j_n,k) f(i,j1,j2,j3,⋯,jn,k)
有几列,需要n+2维的数组来维护。
然而最多可以有9列,所以,如果按照一般的做法,应该开一个十二维的数组来维护。也就是需要
9
×
9
9
×
81
=
282429536481
∗
4
字
节
≈
1
T
B
9\times9^9\times81=282429536481*4字节\approx1TB
9×99×81=282429536481∗4字节≈1TB的内存。
所以我们需要一个更加优秀的方法来维护。
通过观察,我们发现对于 f ( i , j 1 , j 2 , j 3 , ⋯   , j n , k ) f(i,j_1,j_2,j_3,\cdots,j_n,k) f(i,j1,j2,j3,⋯,jn,k)来说, j 1 , j 2 , j 3 , ⋯   , j n j_1,j_2,j_3,\cdots,j_n j1,j2,j3,⋯,jn都是1或0,所以,我们可以很自然地想到二进制。
于是,正解就这么被想到了:用一个二进制数来表示
j
1
,
j
2
,
j
3
,
⋯
 
,
j
n
j_1,j_2,j_3,\cdots,j_n
j1,j2,j3,⋯,jn,于是,一个最多十二维的数组,被我们压缩成了一个三维的数组
f
(
i
,
j
,
k
)
f(i,j,k)
f(i,j,k)
- i i i:第i行
- j j j:该行国王放置的状态
- k k k:前i行有k个国王
但是到这里我们去dp还是不方便,所以,我们需要先dfs预处理出所有的可能,然后再去dp
- s i t [ ] sit[] sit[]:该状态下这一行国王的总数
- g s [ ] gs[] gs[]:这个状态(国王放置的状态)
void dfs(int tot,int sum,int tag)
{
if(tag>=n)//找完了
{
sit[++cnt]=tot;
gs[cnt]=sum;
return ;//这里需要结束
}
dfs(tot,sum,tag+1);//因为这里不用第tag个,所以tag+1
dfs(tot+(1<<tag),sum+1,tag+2);//这里要用第tag个,所以需要tag+2
}
那么我们的状态转移方程就是
f
[
i
]
[
j
]
[
s
]
+
=
f
[
i
−
1
]
[
k
]
[
s
−
g
s
[
j
]
]
f[i][j][s]+=f[i-1][k][s-gs[j]]
f[i][j][s]+=f[i−1][k][s−gs[j]]
- i i i:第i行
- j j j:当前的状态
- k k k:上一行的状态
- s s s:枚举的国王数
最后统计
a
n
s
+
=
f
[
N
]
[
i
]
[
K
]
ans+=f[N][i][K]
ans+=f[N][i][K]
代码实现
#include<cstdio>
#define ll long long
using namespace std;
ll f[10][2000][520];
inline ll read()
{
ll f=1,k=0;
char c=getchar();
while(c>'9'||c<'0')
{
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
return f*k;
}
ll n,m,cnt;
ll sit[2333],gs[2333];
void dfs(int tot,int sum,int tag)
{
if(tag>=n)
{
sit[++cnt]=tot;
gs[cnt]=sum;
return ;
}
dfs(tot,sum,tag+1);
dfs(tot+(1<<tag),sum+1,tag+2);
}
int main()
{
n=read();m=read();
dfs(0,0,0);
for(int i=1;i<=cnt;i++)f[1][i][gs[i]]=1;
for(int i=2;i<=n;i++)
for(int j=1;j<=cnt;j++)
for(int k=1;k<=cnt;k++)
{
if(sit[j]&sit[k])continue;
if(sit[j]&(sit[k]<<1))continue;
if((sit[j]<<1)&sit[k])continue;
for(int s=m;s>=gs[j];s--)f[i][j][s]+=f[i-1][k][s-gs[j]];
}
ll ans=0;
for(int i=1;i<=cnt;i++)ans+=f[n][i][m];
printf("%lld",ans);
return 0;
}
❀完结撒花❀
❀ ❀
❀ ❀