[SCOI2005] 互不侵犯
题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
注:数据有加强(2018/4/25)
输入格式
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式
所得的方案数
样例 #1
样例输入 #1
3 2
样例输出 #1
16
第一次写对状压dp,非常兴奋
代码
#include<bits/stdc++.h>
using namespace std;
long long n,k,dp[10][100][512];
int count2(int x){
long long sum=0;
for(;x;x>>=1){
if(x&1){
sum++;
}
}
return sum;
}
bool availableline(int x){
if(x&(x>>1)){
return false;
}
return true;
}
bool available(int x,int y){
if(x&y||x&(y>>1)||x&(y<<1)){
return false;
}
return true;
}
int main(){
cin>>n>>k;
for(int i=0;i<=(1<<n)-1;i++){
if(availableline(i)){
dp[1][count2(i)][i]=1;
}
}
for(int i=2;i<=n;i++){
for(int j=0;j<=k;j++){
for(int r=0;r<=(1<<n)-1;r++){
if(count2(r)>j) continue;
if(!availableline(r)) continue;
for(int l=0;l<=(1<<n)-1;l++){
if(availableline(l)){
if(available(l,r)){
dp[i][j][r]+=dp[i-1][j-count2(r)][l];
}
}
}
}
}
}
long long ans=0;
for(int i=0;i<=(1<<n)-1;i++){
ans+=dp[n][k][i];
}
cout<<ans;
return 0;
}
这是一道非常经典的状压dp
思路
我们首先要将这个问题分成每一行来看。对于每一行,我们可以枚举出有多少种情况,然后再将每一行之间关联起来,最后再得出答案。
函数
int count2(int x){
long long sum=0;
for(;x;x>>=1){
if(x&1){
sum++;
}
}
return sum;
}
这个函数是用来统计这个十进制数在二进制形式下有多少位1
bool availableline(int x){
if(x&(x>>1)){
return false;
}
return true;
}
这个函数是判断在这一行中放置的国王是否合法,因为国王在横向上不能紧挨着
bool available(int x,int y){
if(x&y||x&(y>>1)||x&(y<<1)){
return false;
}
return true;
}
这个则是用来判断上下两行的国王是否合法
初始化
for(int i=0;i<=(1<<n)-1;i++){
if(availableline(i)){
dp[1][count2(i)][i]=1;
}
}
我们先对图的第一行作特殊处理,对于每一种合法的情况都进行统计。
其中
dp[10][100][512]
dp的第一位代表了我们目前枚举到了第几行
dp的第二位代表了枚举到这一行时,一共已经放置了多少个国王
dp的第三位代表了目前这一行的状态(二进制压缩的状态)
在对图的第一行进行初始化以后,我们接下来就开始进行dp,从第二行开始。
dp过程
我们从第二行开始依次向下枚举
然后在枚举每一行的过程之中,每次把已放置的国王总数从1枚举到k
我们这时枚举这一行的每一个合法状态,状态必须满足availableline,同时满足这个状态的国王放置数小于当前枚举得以放置国王总数,即j。
之后,我们返回去在上一行中,找到能和当前状态匹配的状态,即符合available函数,同时,上一行中状态的已放置国王总数必须为此时枚举到的放置国王总数减去当前这一行放置的国王数量,即上一行状态中已放置国王总数为j-count2(l),当然枚举的上一行状态本身也需要合法
代码如下
for(int i=2;i<=n;i++){
for(int j=0;j<=k;j++){
for(int r=0;r<=(1<<n)-1;r++){
if(count2(r)>j) continue;
if(!availableline(r)) continue;
for(int l=0;l<=(1<<n)-1;l++){
if(availableline(l)){
if(available(l,r)){
dp[i][j][r]+=dp[i-1][j-count2(r)][l];
}
}
}
}
}
}
统计答案
我们通过dp已经知道了在第n行情况下,总共放置k个国王的情况,但是对于最后一行状态也都不同。所以我们需要用一个for循环,统计最后一行的所有情况的答案之和,得到的和就是本题的答案
总结
第一道状压dp题解,本人dp极其差,希望各位理解。不知道为什么,dp就是不会做,更别说那些优化。本人立志今年csp-s第二轮前把dp练熟,包括状压dp