【AcWing算法提高课】1.5状态压缩DP

本文介绍了状态压缩动态规划的概念,通过四个不同的题目:小国王、玉米田、炮兵阵地和愤怒的小鸟,详细阐述了如何在棋盘式和集合式问题中运用状态压缩DP解决复杂问题。每个例子都包含了问题描述、状态表示、状态转移方程以及代码实现,深入浅出地解析了状态压缩DP的应用。
摘要由CSDN通过智能技术生成

零、状态压缩DP

状态压缩:通常用一个二进制数表示,用每一位上的数值代表对应物品的选择(放置/经过/…)情况。

状态压缩DP:与状态机模型相似,当第 i i i 行的状态无法分解时,状态机模型是根据一个时序分解,状态压缩DP是用一个二进制数表示当前状态。

分类:

  1. 棋盘式 (基于联通性):通常是在棋盘/方格中摆放物品,且一个物品对周围其他物品的摆放有限制,如题目一至三
  2. 集合式:维护某些元素是否在集合当中,如求最短汉密尔顿路径时维护当前已经经过哪些点,又如题目四

一、小国王

1064.小国王 题目链接

一个国王只能攻击到相邻的八个格子,不会跨过一行去攻击,因此某一层的摆放方式仅与上一层的摆放方式有关,与上一层之前的摆放方式无关。容易想到DP状态中用状态压缩表示上一层的摆放方式。

闫氏DP分析法:

  1. 状态表示: f [ i , j , s ] f[i,j,s] f[i,j,s]
    (1) 集合:所有只摆了前 i i i 行,共摆了 j j j 个国王,且第 i i i 行摆放方式是 s s s 的方案的集合
    (2) 属性:Count
  2. 状态计算:
    先考虑国王摆放方式需要满足的条件:
    (1) 一行内不能有两个相邻的国王
    (2) 上下两行的国王不能相互攻击到,设两行状态分别为 a , b a,b a,b,则条件等价于 a   a n d   b = 0 a\ and\ b=0 a and b=0 (上下不能相互攻击到) 且 a   o r   b a\ or\ b a or b 后的结果中不能有两个相邻的 1 1 1 (斜对角不能攻击到)
    因此有 f [ i , j , s ] = ∑ k 与 s 满足上述条件 f [ i − 1 , j − c n t ( k ) , k ] f[i,j,s]=\sum\limits_{k与s满足上述条件}f[i-1,j-cnt(k),k] f[i,j,s]=ks满足上述条件f[i1,jcnt(k),k],其中 c n t ( k ) cnt(k) cnt(k) 代表二进制数 k k k 1 1 1 的个数。
  3. 初始化: f [ 0 , 0 , 0 ] = 1 f[0,0,0]=1 f[0,0,0]=1
  4. 最终答案: ∑ s 满足条件 ( 1 ) f [ n , m , s ] \sum\limits_{s满足条件(1)}f[n,m,s] s满足条件(1)f[n,m,s],一个更简便的方法是在循环行数时循环至 n + 1 n+1 n+1,最终答案为 f [ n + 1 , m , 0 ] f[n+1,m,0] f[n+1,m,0] (即在前 n + 1 n+1 n+1 行共放了 m m m 个国王,且第 n + 1 n+1 n+1 行什么也不放的方案数)

在这里插入图片描述
在DP之前,先预处理出所有合法状态,再预处理出两个状态之间的合法转移,便于之后DP中的枚举。

代码实现:

#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 12, M = 1 << N;
typedef long long ll;

int n, m;
ll f[N][N * N][M];
vector <int> state;
int cnt[M];
vector <int> head[M];

bool check(int state){
     //判断一个状态是否合法
    for (int i = 0; i < n; i ++)
        if ((state >> i & 1) & (state >> i + 1 & 1))  //如果有连续两个1,不合法
            return 0;
    return 1;
}

int count(int state){
     //计算一个状态中1的个数
    int res = 0;
    for (int i = 0; i < n; i ++)
        if (state >> i & 1) res ++;
    return res;
}

int main(){
   
    scanf("%d %d", &n, &m);

    for (int i = 0; i < 1 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值