P1896 [SCOI2005] 互不侵犯 方法记录

原题链接

[SCOI2005] 互不侵犯

题目描述

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

注:数据有加强(2018/4/25)

输入格式

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

输出格式

所得的方案数

样例 #1

样例输入 #1

3 2

样例输出 #1

16

审题

由题目可得:棋盘上国王割据的过程是随着“阶段”的增长,在每个状态维度上不断扩展的。在任意时刻,已经求出最优解的状态与尚未求出最优解的状态在各维度上的分界点组成了DP扩展的“轮廓”。而在这道题中,我们需要经济地保存棋盘的详细状态,所以想到状态压缩DP。

状态压缩DP介绍

以本题为例,假如有一行的国王放置状态如下

这里使用两个数组记录状态

sit[i]表示有无国王的二进制状态
sta[i]表示国王的个数
则如上图\(sit[i]=(100101)_2=37\),\(sta[i]=3\).
这样一来,一行中国王的状态就被压缩到一个维度中

推导转移方程

\(f[i][j][s]+=f[i-1][k][s-sta[j]]\)
其中,i表示第i行,j表示当前国王状态,s表示当前国王个数,结合sit[],sta[]表示。

预处理每一个状态

dfs(x,num,cur)元素含义:x表示递归的层数,num表示已经上场的国王数量,cur表示当前遍历到的位置(行)

点击查看代码
void dfs(int x,int num,int cur)//预处理每一个状态
{
    if(cur>=n)//超出边界,处理完毕 
    {
        sit[++cnt]=x;
        sta[cnt]=num;
        return ;
    }
    dfs(x,num,cur+1);//当前位置不放国王:国王数量不变,下一个位置可以放国王,故指向下一个位置 
    dfs(x+(1<<cur),num+1,cur+2);//当前位置放国王:国王数量+1,下一个位置不可以放国王,故指向下下个位置 
}

判断冲突情况

知识点:位运算

点击查看代码
if(sit[j]&sit[x]) continue;//x:当前行位置国王放置情况;j:正上方位置国王放置情况。上下冲突 
if((sit[j]<<1)&sit[x]) continue;//左上右下冲突 
if(sit[j]&(sit[x]<<1)) continue;//右上左下冲突 
以下为图示

程序大致框架:

输入->预处理->DP循环(判断冲突)->统计答案->输出。
完整带注释代码

点击查看代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
using namespace std;
int sit[2000],sta[2000];
//sit[i]表示有无国王的二进制状态
//sta[i]表示国王的个数 
int cnt=0;
int n,k;
long long f[10][2000][100]={0};
//dfs(x,num,cur)元素含义:x 递归的层数;num 已经上场的国王数量;cur当前遍历到的位置(行) 
void dfs(int x,int num,int cur)//预处理每一个状态
{
    if(cur>=n)//超出边界,处理完毕 
    {
        sit[++cnt]=x;
        sta[cnt]=num;
        return ;
    }
    dfs(x,num,cur+1);//当前位置不放国王:国王数量不变,下一个位置可以放国王,故指向下一个位置 
    dfs(x+(1<<cur),num+1,cur+2);//当前位置放国王:国王数量+1,下一个位置不可以放国王,故指向下下个位置 
}
//f[i][j][s]+=f[i-1][k][s-sta[j]]状态转移方程
//i=第i行;j=当前国王的状态;s=当前国王的个数,可以用sit[]sta[]表示 
 
int main()
{
    scanf("%d%d",&n,&k);
    dfs(0,0,0);//预处理 
    for(int i=1;i<=cnt;i++)f[1][i][sta[i]]=1;//处理第一行:防止越界 
    for(int i=2;i<=n;i++)
        for(int j=1;j<=cnt;j++)
            for(int x=1;x<=cnt;x++)//x为j正下方的位置 
            {
                if(sit[j]&sit[x]) continue;//x:当前行位置国王放置情况;j:正上方位置国王放置情况。上下冲突 
                if((sit[j]<<1)&sit[x]) continue;//左上右下冲突 
                if(sit[j]&(sit[x]<<1)) continue;//右上左下冲突 
                for(int s=sta[j];s<=k;s++)f[i][j][s]+=f[i-1][x][s-sta[j]];
            }
    long long ans=0;
    for(int i=1;i<=cnt;i++)ans+=f[n][i][k];//n行矩阵,放置k个国王的情况总数 
    printf("%lld",ans);
    return 0;
}
这个视频给我的理解带来极大的帮助

(而且声音很甜很好听)

https://www.bilibili.com/video/av681073078/?vd_source=b9e2e351c4ebc946cfd86808c70b65ce

posted @ 2022-09-29 19:30  Fish4174  阅读(30)  评论(0编辑  收藏  举报