动态规划算法刷题笔记【状压dp】

这篇具有很好参考价值的文章主要介绍了动态规划算法刷题笔记【状压dp】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

二进制枚举子集

动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++
a&1 == 1 判断是否为奇数,如果为1,则为奇数因为奇数二进制末位一定是1,所以 与1 得到的结果是1

动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++
动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++
这里,1<<14——214——第15位是1,可以表示14个1
i&(1<<j)—— 从0开始是因为,原本第1位就是1。所以j=0时,对应的就是 i 的最低位

动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++

状态压缩

动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++

旅行商问题

动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++
动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++
动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++

F l o y d Floyd Floyd算法:

动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++

方格取数问题

动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++
动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++
动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++
动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++
n o w ∣ f l a g = = f l a g now | flag == flag nowflag==flag —— (1代表可以选择,0代表不可以选择):

10110 10110 10110
00110 00110 00110
= 10110 = = f l a g = 10110 == flag =10110==flag

10110 10110 10110
01001 01001 01001
= 11111 ! = f l a g = 11111 != flag =11111!=flag

使用条件

  • 状态需要有一定的状态单元。 即一个状态应该是保存一个集合,其中的元素值对应着0或1,例如我们常见的棋盘,我们可以用0或1来表示棋子的放置状态。而整个集合即是一个01串,即二进制数,我们通常用十进制表示。那么我们再进行状态转移或者判断的时候,需要先将十进制转化为二进制,再将二进制转化为十进制
  • 题目中限制的集合大小不会超过20。 如果用二进制表示状态,那么集合大小为20的二进制状态有220-1,已经达到1e7的数量级了
  • 具有动态规划的特性。 对于动态规划,一般都是要求最优化某个值,具有最优子结构的性质。同时也需要满足状态转移的特性,而不是前一个状态毫无关系的

题目

[SCOI2005] 互不侵犯

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

输入格式

只有一行,包含两个数N,K ( 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

思路

  • 还是比较模板的一道题
  • 国王的意思是:选定了格子,然后不能相邻(斜上方、斜下方)。跟方格取数差不多
  • 首先对第1行进行处理,这样后面的行才能使用模板
  • 循环模板:1. 枚举行。——2. 枚举上个阶段放了的国王数。——3. 枚举本阶段的状态。——4. 在枚举本阶段状态的同时,枚举上一阶段的状态,维护状态。

题解文章来源地址https://www.toymoban.com/news/detail-670921.html

#include<bits/stdc++.h>
using namespace std;
long long ans,n,m,k,f[10][100][1<<9+5];
int lowbit(int x){
	int tmp=0;
	while(x) tmp++,x-=x&(-x);
	return tmp;
}
int main(){
	scanf("%lld%lld",&n,&k);
	m=(1<<n)-1;
	for(int i=0;i<=m;++i){
		if(!(i&(i<<1))&&lowbit(i)<=k) 
		f[1][lowbit(i)][i]=1;
	} 
	//处理出第一行的所有情况 
	for(int i=2;i<=n;++i)//枚举行 
	for(int j=0;j<=k;++j)//枚举上个阶段放了的国王数 
	for(int x=0;x<=m;++x){//枚举本阶段的状态 
		if(x&(x<<1)) continue;//本阶段不能互相伤害 
		for(int y=0;y<=m;++y){//枚举上一个阶段的状态 
			if(x&y||x&(y<<1)||x&(y>>1)) continue;//本状态和上一个状态不能冲突
			if(j+lowbit(x)>k) continue;//本状态新放的国王数目+上个阶段国王数小于k 
			f[i][j+lowbit(x)][x]+=f[i-1][j][y];  //本状态加上一状态数 
		}
	}
	for(int j=0;j<=m;++j) ans+=f[n][k][j];
	printf("%lld",ans);
}

[NOI2001] 炮兵阵地

司令部的将军们打算在 N × M N\times M N×M 的网格地图上部署他们的炮兵部队。

一个 N × M N\times M N×M 的地图由 N N N M M M 列组成,地图的每一格可能是山地(用 H \texttt{H} H 表示),也可能是平原(用 P \texttt{P} P 表示),如下图。

在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

动态规划算法刷题笔记【状压dp】,算法刷题笔记,算法,动态规划,c++

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。

图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。

现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入格式

第一行包含两个由空格分割开的正整数,分别表示 N N N M M M

接下来的 N N N 行,每一行含有连续的 M M M 个字符,按顺序表示地图中每一行的数据。

输出格式

一行一个整数,表示最多能摆放的炮兵部队的数量。

样例输入 #1

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

样例输出 #1

6

提示

对于 100 % 100\% 100% 的数据, N ≤ 100 N\le 100 N100 M ≤ 10 M\le 10 M10,保证字符仅包含 PH

思路

  • 这道题还是有些复杂的
  • 按照思路:1. 初始化(对第1行操作、枚举合法状态)。2. 状态转移。(循环判断后面的行是否合法——判断 i-1,i-2)

题解

#include<bits/stdc++.h>
using namespace std;
int n,m,num[60];
char s[110][15]; 
int rec[110];
int state[70],top;
int dp[110][70][70];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<(1<<m);i++){
		if((i&(i<<1)||(i&(i<<2))))continue; 
		int k=i;
		while(k){
			++num[top];
			k&=(k-1);                 //这一步是去掉k二进制下的一个1
		}
		state[top++]=i;
	}
	for(int i=0;i<n;i++){
		cin>>s[i];
		for(int j=0;j<m;j++)
			if(s[i][j]=='H')
		 	rec[i]|=(1<<j); 
	}
	for(int i=0;i<top;i++) {
		if(state[i]&rec[0])continue;
		dp[0][0][i]=num[i];
	}
	for(int i=1;i<n;i++){
		for(int j=0;j<top;j++) {
			if(state[j]&rec[i])continue; 
			for(int k=0;k<top;k++) {
				if(state[j]&state[k])continue;
				for(int t=0;t<top;t++) {
					if(state[j]&state[t])continue; 
					if(state[k]&state[t])continue;
					dp[i][k][j]=max(dp[i][k][j],dp[i-1][t][k]+num[j]); 
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<n;i++)
		for(int j=0;j<top;j++)
			for(int k=0;k<top;k++)
				ans=max(ans,dp[i][j][k]);
	printf("%d",ans);
}

[蓝桥杯 2019 省 A] 糖果

糖果店的老板一共有 M M M 种口味的糖果出售。为了方便描述,我们将 M M M 种口味编号 1 1 1 M M M

小明希望能品尝到所有口味的糖果。遗憾的是老板并不单独出售糖果,而是 K K K 颗一包整包出售。

幸好糖果包装上注明了其中 K K K 颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。

给定 N N N 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。

输入格式

第一行包含三个整数 N N N M M M K K K

接下来 N N N 行每行 K K K 这整数 T 1 , T 2 , ⋯   , T K T_1,T_2, \cdots ,T_K T1,T2,,TK,代表一包糖果的口味。

输出格式

一个整数表示答案。如果小明无法品尝所有口味,输出 − 1 -1 1

样例输入 #1

6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2

样例输出 #1

2

提示

对于 30 % 30\% 30% 的评测用例, 1 ≤ N ≤ 20 1 \le N \le 20 1N20

对于所有评测样例, 1 ≤ N ≤ 100 1 \le N \le 100 1N100 1 ≤ M ≤ 20 1 \le M \le 20 1M20 1 ≤ K ≤ 20 1 \le K \le 20 1K20 1 ≤ T i ≤ M 1 \le T_i \le M 1TiM

蓝桥杯 2019 年省赛 A 组 I 题。

思路

  • 看到这道题,能想到是状压dp,因为 M , K M,K M,K的值都挺小的,算是一种暗示吧。但是一直弄不清楚状态应该设置为什么,状态转移应该从什么样的状态转移到什么样的状态
  • 看了别人的思路:
    • 状态压缩,把每一包糖果看成一个集合,把这个集合用一个整数表示。
    • 这个整数的二进制形式的第i位为1表示这包糖果里有第i种糖果
    • 最多一共有2^20 = 1048576种状态
    • 用已有的状态去更新加上第i包糖果后的状态

题解

#include <bits/stdc++.h>
using namespace std;
int n, m, k, x;
int a[400];
int dp[1 << 21];
int main(){
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i++){
		for (int j = 1; j <= k; j++){ 
			cin >> x;
			a[i] |= 1 << (x - 1);     // 以二进制形式表示这包糖果中的每颗糖果
		}
	}
	memset(dp, 0x3f, sizeof(dp)); 
	dp[0] = 0; 
	for (int i = 1; i <= n; i++){
		for (int j = 0; j < (1 << m); j++){  
			if (dp[j] > n) continue;
			dp[j | a[i]] = min(dp[j | a[i]], dp[j] + 1);
		}
	}
	if (dp[(1 << m) - 1] > n) cout << -1;   
	else cout << dp[(1 << m) - 1];       
}

[蓝桥杯 2022 省 B] 李白打酒加强版

话说大诗人李白,一生好饮。幸好他从不开车。

一天,他提着酒壶,从家里出来,酒壶中有酒 2 2 2 斗。他边走边唱:

无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。

这一路上,他一共遇到店 N N N 次,遇到花 M M M 次。已知最后一次遇到的是花,他正好把酒喝光了。

请你计算李白这一路遇到店和花的顺序,有多少种不同的可能?

注意:壶里没酒( 0 0 0 斗)时遇店是合法的,加倍后还是没酒;但是没酒时遇花是不合法的。

输入格式

第一行包含两个整数 N N N M M M

输出格式

输出一个整数表示答案。由于答案可能很大,输出模 1000000007 1000000007 1000000007(即 1 0 9 + 7 10^9+7 109+7)的结果。

样例输入 #1

5 10

样例输出 #1

14

提示

【样例说明】

如果我们用 0 代表遇到花,1 代表遇到店, 14 14 14 种顺序如下:

010101101000000
010110010010000
011000110010000
100010110010000
011001000110000
100011000110000
100100010110000
010110100000100
011001001000100
100011001000100
100100011000100
011010000010100
100100100010100
101000001010100

【评测用例规模与约定】

对于 40 % 40 \% 40% 的评测用例: 1 ≤ N , M ≤ 10 1 \leq N, M \leq 10 1N,M10

对于 100 % 100 \% 100% 的评测用例: 1 ≤ N , M ≤ 100 1 \leq N, M \leq 100 1N,M100

蓝桥杯 2022 省赛 B 组 I 题。

思路

  • 思路就像经典的李白打酒一样,状压,然后枚举相应状态是否符合条件。但是,这样最多就只能过40%数据,因为要开 2 n + m 2^{n+m} 2n+m,按照传统的方法来,肯定MLE
  • 看了别人的思路:不用状态压缩,因为没办法压缩
    • 首先我们有必要对酒的奇偶性进行讨论,因为当走到第i个位置时酒的数量为奇,则第i个位置不可能为店,因为无论是奇数还是偶数,乘以2得到的数都是一个偶数,所以只有当k是偶数时第i个位置才可能是店
    • 假如第i个位置是店,那么这个时候在第i-1个位置的酒的数量就是k/2,而遇到花的数量跟第i-1个位置是一样的都是j,这个很好理解
    • 下面再看一下第i个位置是花的情况,那么第i-1个位置的酒的数量一定是k+1,因为在第i个位置消耗了1,而到达第i个位置遇到的花的数量就要比第i-1个位置遇到花的数量多1,因为我们现在是在假设第i个位置是花

题解

#include<bits/stdc++.h>
using namespace std;
const int N=113;
const int mod=1e9+7;
long long f[N*2][N][N];//f[i][j][k]表示走到了第i个位置,遇到了j个花,还剩 k斗酒的合法方案数 
int main(){
	f[0][0][2]=1;
	int n,m;
	cin>>n>>m;
	for(int i=1;i<n+m;i++)
	for(int j=0;j<m;j++)
	for(int k=0;k<=m;k++)//酒的数量不能超过花的数量,否则就算之后一直是花也喝不完 
	{
		if(!(k&1))//k是偶数,则第i个位置可以是店,否则不可以是店
			f[i][j][k]=(f[i][j][k]+f[i-1][j][k>>1])%mod;
		if(j>=1)//无论k是奇数还是偶数,第i个位置都可以是花 
			f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k+1])%mod;
	}
	printf("%lld",f[n+m-1][m-1][1]);
}

总结

  • 首先看到动态规划的题目中有数据量 ≤ 20 ≤20 20的情况下,很有可能用到状态压缩
  • 先进行状态压缩,然后再考虑动态规划的思路

到了这里,关于动态规划算法刷题笔记【状压dp】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 算法基础复盘笔记Day11【动态规划】—— 区间DP、计数类DP、树形DP、记忆化搜索

    ❤ 作者主页:欢迎来到我的技术博客😎 ❀ 个人介绍:大家好,本人热衷于 Java后端开发 ,欢迎来交流学习哦!( ̄▽ ̄)~* 🍊 如果文章对您有帮助,记得 关注 、 点赞 、 收藏 、 评论 ⭐️⭐️⭐️ 📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉 1. 题目

    2024年02月01日
    浏览(29)
  • AcWing算法学习笔记:动态规划(背包 + 线性dp + 区间dp + 计数dp + 状态压缩dp + 树形dp + 记忆化搜索)

    算法 复杂度 时间复杂度0(nm) 空间复杂度0(nv) 代码 算法 通过滚动数组对01背包朴素版进行空间上的优化 f[i] 与 f[i - 1]轮流交替 若体积从小到大进行遍历,当更新f[i, j]时,f[i - 1, j - vi] 已经在更新f[i, j - vi]时被更新了 因此体积需要从大到小进行遍历,当更新f[i, j]时,f[i - 1,

    2024年02月21日
    浏览(33)
  • 【动态规划】NK刷题之DP7 连续子数组的最大乘积

    ❤️博客主页: 小镇敲码人 🍏 欢迎关注:👍点赞 👂🏽留言 😍收藏 🌞在一切变好之前,我们总要经历一些不开心的日子,这段日子也许很长,也许只是一觉醒来。有时候,选择快乐,更需要勇气。 🍉 如果你也迷失在了路上,对人生充满了迷惘,不要害怕,冷静下来,

    2024年02月08日
    浏览(36)
  • DP算法:动态规划算法

    (1)确定初始状态 (2)确定转移矩阵,得到每个阶段的状态,由上一阶段推到出来 (3)确定边界条件。 蓝桥杯——印章(python实现) 使用dp记录状态,dp[i][j]表示买i张印章,凑齐j种印章的概率 i表示买的印章数,j表示凑齐的印章种数 情况一:如果ij,不可能凑齐印章,概

    2024年02月07日
    浏览(37)
  • ★动态规划(DP算法)详解

    什么是动态规划:动态规划_百度百科 内容太多了不作介绍,重点部分是无后效性,重叠子问题,最优子结构。 问S-P1和S-P2有多少种路径数,毫无疑问可以先从S开始深搜两次,S-P1和S-P2找出所有路径数,但是当这个图足够大,那就会超时。 动态规划旨在用 空间换时间 ,我们

    2024年02月04日
    浏览(38)
  • 算法——动态规划(DP)——递推

    动态规划常用于解决优化问题。 动态规划通常以自底向上或自顶向下的方式进行求解。 自底向上的动态规划从最简单的子问题开始,逐步解决更复杂的问题,直到达到原始问题。 自顶向下的动态规划则从原始问题出发,分解成子问题,并逐步求解这些子问题。 动态规划算法

    2024年01月20日
    浏览(43)
  • 动态规划——数位DP 学习笔记

    引入 数位 DP 往往都是这样的题型:给定一个区间 ([l, r]) ,求这个区间中满足某种条件的数的总数。 简单的暴力代码如下: 而当数据规模过大,暴力枚举就 (mathbb T) 飞了,因此引入数位 DP: 概念 数位(digit):对于十进制,即把一个数字按照个位、十位、百位等,一位

    2024年02月08日
    浏览(38)
  • 动态规划——区间DP 学习笔记

    不含四边形不等式优化。 线性动态规划的局限性在于,它只能顺推或倒退,而不能有子区间依赖的问题。 区间动态规划是线性动态规划的扩展,它将问题划分为若干个子区间,并通过定义状态和状态转移方程来求解每个子区间的最优解,最终得到整个区间的最优解。 区间动

    2024年02月08日
    浏览(31)
  • 动态规划——树形DP 学习笔记

    前置知识:树基础。 树形 DP,即在树上进行的 DP,最常见的状态表示为 (f_{u,cdots}) ,表示以 (u) 为根的子树的某个东东。 本文将讲解一些经典题目(树的子树个数、树的最大独立集、树的最小点覆盖、树的最小支配集、树的直径、树的重心、树的中心),以及一些常见形

    2024年02月08日
    浏览(27)
  • 动态规划——斜率优化DP 学习笔记

    适用于求解最优解(最大、最小)问题。 可以将转移方程可以化为 (left[begin{array}{rl} 仅与 space i space 有关 是我们想要最大/最小化的 \\\\ 仅与 space j space 有关 是已知的 \\\\ 与 space i space 和 space j space 都有关 是两项相乘 end{array}right]) 三部分的, 都可以考虑用斜率优化

    2024年02月08日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包