[SCOI2005]互不侵犯 状压DP分析+题解

该博客介绍了如何运用状态压缩动态规划(状压DP)解决一个在9x9棋盘上放置K个互不攻击的国王的问题。通过将每行的状态用二进制表示,并分析状态转移方程,博主详细阐述了如何预处理合法状态并进行状态转移,最终实现求解方案数的高效算法。文章适合初学者理解状压DP的应用。
摘要由CSDN通过智能技术生成
● 本题解配合状压DP总结食用,用于配合总结分析!
➡原题传送门:[[SCOI2005]互不侵犯](https://www.luogu.com.cn/problem/P1896)

题目描述

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

输入格式

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

输出格式

所得的方案数

输入输出样例

输入 #1

3 2

输出 #1

16

题目分析

题目很短,分析之后可以写爆搜,但是强数据难免RE,因此考虑DP的做法。

N的范围小于等于9,那么典型的状压DP特征便表现出来了,实际上这是一道非常优质的状压DP入门题

下面对状压的过程进行详细的分析:首先什么是状压:

状压-即通过将状态压缩为整数来达到优化转移的目的。

对于本题,我们可以用一个二维数组来储存图,数组的对应位置表示棋盘上的实际位置,这事的状态并未进行压缩。

由于N的范围较小 ( N < = 9 ) (N <= 9) (N<=9),我们可以很自然的反映出来:我们可以用二进制整数来表示每一行的状态,如图所示:

二进制表示图

在上述的行状态下,我们通过一个八位二进制整数来表示了一行中各个位置的状态:10101010

既然这种方式成立,那么我们就可以根据二进制数各位表示的状态进行状态转移

了解了何为状压,接下来就是DP的分析

我们采用闫氏DP分析法进行分析:

DP分析
状态表示
状态计算
1.确定dp数组元素含义:我们已经对行状压, 因此我们用dp i, j, l 表示选到第i行,第i行状态为l,用了j个棋子
2.确定dp数组维护的属性:表示当前在 dp i, j, l 选择下的方案数
1.确定状态转移方程,见下方分析
2.确定初始状态, 确定边界条件: dp 0, 0, 0 = 1

状态转移方程的推导

首先,假设在第 i − 1 i-1 i1行的的第 j j j列放置一枚棋子,那么如图所示,第j行会出现不合法放置位置

不合法状态

即:第i行的 j − 1 , j , j + 1 j-1, j, j+1 j1,j,j+1位置是非法的,那么在状压后如何对这些非法状态进行检测呢?相信你一定会立即反应出来:二进制运算

在这里插入图片描述

对于 j j j位置的棋子,我们通过直接进行与运算进行检测,如果为1证明状态非法;

对于 j − 1 j - 1 j1位置的棋子,我们通过对L1左移进行检测(也可以对L2进行右移,原理相同),如果为1证明状态非法;

对于 j + 1 j + 1 j+1位置的棋子,我们通过对L1右移进行检测(也可以对L2进行左移,原理相同),如果为1证明状态非法;

我们可以总结:当L2由L1进行转移时,必须满足的条件是:

(L2 & L1 == 0) && ((L2 << 1) & L1 == 0) && ((L2 >> 1) & L1 == 0)

化简得:

((L2|(L2 >> 1)|(L2 << 1)) & L1 == 0)

同时,第 i − 1 i-1 i1行的 j − 1 , j + 1 j-1, j+1 j1,j+1位置也是非法的,我们同样可以通过移位与运算进行检验:

合法的状态:

在这里插入图片描述

非法的状态:

在这里插入图片描述

满足条件:

((L << 1) | (L >> 1)) & L == 0

由于我们向下扫描递推,因此自上向下诞生合法状态,无需在考虑上一行是否合法。因此,下一步L2满足的状态我们全部总结完毕

因为第二个条件只与状态的情况有关,所以我们可以预处理这个东西,跑DP的时候将所有满足第二条性质的状态拿出来看看是否再满足第一条性质就好了;

设当前行的状态为 j j j ,上一行的状态为 x x x ,可以得到下面的转移方程: f ( i , j , l ) = ∑ f ( i − 1 , x , l − s t a ( x ) ) f(i,j,l) = \sum f(i-1,x,l-sta(x)) f(i,j,l)=f(i1,x,lsta(x))

AC Code

#include <bits/stdc++.h>
using namespace std;
long long sta[2005], sit[2005], f[15][2005][105];
int n, k, cnt = 0;
void dfs(int x, int num, int cur)
{
    if (cur >= n){ // 有新的合法状态
        sit[++cnt] = x;
        sta[cnt] = num;
        return;
    }
    dfs(x, num, cur + 1); // cur位置不放国王
    dfs(x + (1 << cur), num + 1, cur + 2); // cur位置放国王,与它相邻的位置不能再放国王
}
int main()
{
    cin >> 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 l = 1; l <= cnt; l++){
                if (sit[j] & sit[l]) continue;
                if ((sit[j] << 1) & sit[l]) continue;
                if (sit[j] & (sit[l] << 1)) continue;
                // 排除不合法转移
                for (int p = sta[j]; p <= k; p++) f[i][j][p] += f[i - 1][l][p - sta[j]];
            }
    long long ans = 0;
    for (int i = 1; i <= cnt; i++) ans += f[n][i][k]; // 累加答案
    cout << ans << endl;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HeartFireY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值