0
点赞
收藏
分享

微信扫一扫

蓝桥杯十大常见天阶功法——炎之呼吸.叁之型.动态规划--(上篇)

🔔燃烧炽热心灵的,照亮动态规划的

在这里插入图片描述



往期精彩

🔖蓝桥杯十大常见天阶功法——水之呼吸.壹之型.递归
🔖蓝桥杯十大常见天阶功法——虫之呼吸.贰之型.二分
🔖 蓝桥杯十大常见天阶功法——音之呼吸.肆之型.模拟

💓叁之型上篇总览

在这里插入图片描述

💓动态规划简述



系统的提一下思考方向是很多滴,选一种顺手的~
不管黑猫白猫了,只要能Ac的,都是好喵喵~

在这里插入图片描述

听闫总讲课说,动态规划可以理解为对暴力的优化

因为暴力是逐个逐个的处理每种情况,可想而知,其中存在大量的重复情况。动态规划就是将一些相似的情况化零为整了。
acwing师兄总结了这张DP分析图十分全面,感谢师兄🌹🌹🌹

在这里插入图片描述

大哥

💓壹 - 试题 算法提高 夺宝奇兵

线性动态规划模型——经典的"数字三角形"的翻版,因为状态转移过程十分清晰,十分适合作为入门之选。

🌱题目描述

题目 1514: 蓝桥杯算法提高VIP-夺宝奇兵

题目 1514: 蓝桥杯算法提高VIP-夺宝奇兵

🌴解题报告

记住这个一种线性DP的模型。它的背景是数字三角形,可能是大多数学动态规划的小伙伴的启蒙题目了吧。
数字三角形

将上述文字做成一张图,就是一张这种效果的闫式DP分析图:
数字三角形

因为,首先我们整体是一个归类的思想,所有从顶点走到图示中(4,2)这点的方案,算作一个集合。
也就是,无论是走的7->3->8->7还是的7->8->1->7,都是看做同一个类

🌵参考代码(C++版本)

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;
const int N = 510,INF = 1e9;
int a[N][N];
int f[N][N];
int n;


int main()
{
    scanf("%d",&n);
    
    //输入
    for(int i = 1; i <= n;i++) 
        for(int j = 1; j <= i;j++) cin >> a[i][j];
        
    //初始化数组为负无穷,注意左边边界的空白和右边边界的空白也要初始化的
    //比如(2,2)位置的8,因为是集合的思想,它也可能从右上转移过来
    //但是那儿实际是没有数据的,因此处理为负无穷
    for(int i = 1; i <= n+1;i++)    
        for(int j = 0; j<=i+1;j++) f[i][j] = -INF;
    
    //开始DP
    f[1][1] =  a[1][1];
    for(int i = 2; i <= n;i++)  
        for(int j = 1;j <= i; j++)
        f[i][j] = max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
    
    //枚举最后一行,找到最大值
    int res = -INF;
    for(int i = 1;i <= n;i++)  res = max(res,f[n][i]);
    
    cout << res <<endl;
    return 0;
}

💓贰 - 历届试题 数字三角形【第十一届】【省赛】【C组】

线性动归规划——数字三角形的进阶+掌握数论小知识

🌱题目描述

试题 历届试题 数字三角形【第十一届】【省赛】【C组】

原题传送门

🌴解题报告

在这里插入图片描述

在这里插入图片描述

一个小小的脑筋急转弯

那么我就不重复的进行状态表示和状态计算的分析了,直接将DP分析图放上了。

数字三角形

🌵参考代码(C++版本)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 110,INF = 0x3f3f3f3f;
int a[N][N];
int f[N][N];
int n;

int main()
{
	cin >> n;
	
	for(int i = 1; i <= n;i++)
		for(int j = 1; j <= i;j++) cin >> a[i][j];

	//初始化
	for(int i = 1; i <= n+1;i++)
		for(int j = 1; j <= i+1;j++)
		f[i][j] = -INF;

	f[1][1] = a[1][1];
	
	//开始DP
	for(int i = 2;i <= n;i++)
		for(int j = 1; j <= i;j++)
			f[i][j] = max(f[i-1][j-1]+a[i][j] , f[i-1][j]+a[i][j]);

	int ans = 0;
	//输入最后一排的个数是偶数,那么中间两个选
	if(n % 2 == 0) ans = max(f[n][n/2],f[n][n/2+1]);
	//最后一排是奇数,那么就输出中间位置上的数据
	else ans = f[n][n/2+1];

	cout << ans << endl;
	
	
	return 0;
}

💓叁 - 历届真题 蓝肽子序列【第十一届】【决赛】【研究生组】

线性动态规划中的最长公共子序列模型 + 最大值这个属性是可以重复的

🌱题目描述

在这里插入图片描述

原题传送门

🌴解题报告

在这里插入图片描述

最长公共子序列的常规模型的分析方式

假如有两个子序列,分别叫做 a a a b b b

状态表示:

状态计算:

状态计算对应的是集合划分的过程,划分的依据是当前能利用起来的最后一个不同点

这里有个划分细节,是以前在y总的课中听到的,可以注意,心中有个底就好,现阶段不必纠结的,有个印象,能Ac比啥都好。
在这里插入图片描述

对于本题而言:可以依据a[i]和b[j]是否包含在当前的子序列中来划分。

情况一a[i] 和 b[j]都不在此时状态转移方程为f[i-1][j-1]

情况二a[i]不在,b[j]在此时状态转移方程可以写为:f[i-1][j]

这就是我上文说的,在划分的时候,本来是要严格遵守不重不漏的原则,但是对于最大值而言,是可以重复的。

情况三a[i]在,b[j]不在此时状态转移方程可以写为:f[i][j-1]

情况四a[i]和b[j]都在此时状态转移方程为f[i-1][j-1] + 1


综上所述,可以得到这闫式DP分析图。
在这里插入图片描述

好了,清楚了这个以后,就可以着手落实代码啦~

🌵参考代码(C++版本)

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;

int f[N][N];
string s1, s2;
string str1[N], str2[N];
int cnt1, cnt2;

int main()
{
	//输入
    cin >> s1 >> s2;
    
    //获取两个字符串长度
    int len1 = s1.length(), len2 = s2.length();
    
	//分割蓝肽
    for (int i = 0; i < len1;)
    {
        //遇到大写字母,就是遇到蓝肽了
        if (s1[i] >= 'A' && s1[i] <= 'Z')
        {
            str1[++cnt1] += s1[i++];
            while (s1[i] >= 'a' && s1[i] <= 'z') str1[cnt1] += s1[i++];
        }
    }
    
    for (int i = 0; i < len2;)
    {
        if (s2[i] >= 'A' && s2[i] <= 'Z')
        {
            str2[++cnt2] += s2[i++];
            while (s2[i] >= 'a' && s2[i] <= 'z') str2[cnt2] += s2[i++];
        }
    }
    
    
    for (int i = 1; i <= cnt1; i++)
        for (int j = 1; j <= cnt2; j++)
        {
            if (str1[i] == str2[j]) f[i][j] = f[i - 1][j - 1] + 1;
            else f[i][j] = max(f[i][j - 1], f[i - 1][j]);
        }
        
    cout << f[cnt1][cnt2] << endl;
    
    return 0;
}

💓肆 - 历届真题 砝码称重【第十二届】【省赛】【A/B/C组】

选择模型中的01背包的变型

🌱题目描述

试题 历届真题 砝码称重【第十二届】【省赛】【A/B/C组】

原题传送门

🌴解题报告

看到题目中说到让咱们寻找一个最优解,那么这个时候,可以尝试向着动态规划的方向去思考。

系统温习背包问题:

对于本题:

状态表示:
集合:集合f[i][j]表示的含义是从前i个砝码中进行选择且总体积为j的所有方案的集合

属性:这个集合所存储的属性是集合是否非空。表示从前i个砝码中选出总重量为j的方案是否存在,可以很明显的看出,是一个bool值。

状态计算:

状态计算对应的是对咱们规定的集合的划分,划分的依据大多是最后一个不同点。

比如本题,最后一个不同点就是当前这个砝码是怎么进行放置:

将如上的步骤进行整理的,那么就可以得到这张闫式DP分析图
砝码称重
同样的,对应一定会选择的砝码i,依旧先采用先剖除它并不会影响整体格局的思想,也就是i-1,对应修改总和j受到的影响。

🌵参考代码(C++版本)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110,M = 100010;
int n,m;
int w[N];//存储砝码质量的数组
bool f[N][2 * M];//表示状态的数组


int main()
{
    cin  >> n;
	//DP环节会有减一的,都建议从1开始获取值
    for(int i = 1; i <= n;i++) cin >> w[i],m += w[i];
    
    //初始化,这行代码的意思是,从前0个砝码中选出总和为0的方案是存在的
	//M 只是都要加的一个偏移量
    f[0][M] = true;
	//枚举n个砝码
    for(int i =1; i <= n;i++)
    {
    	//枚举会出现的重量,因为可能为负,所有统计加上了一个偏移量M
    	for(int j = -m; j <= 2 * m ;j++)
    	{
			//情况一:不选当前砝码的集合不是空
			if(f[i-1][j+M])
			{
				f[i][j+M] = true;
				continue;
			}
			if(j - w[i] >= -m &&f[i-1][j- w[i] + M])
			{
				f[i][j+M] = true;
				continue;
			}
			if(j + w[i] <= m && f[i-1][j+w[i] + M])
			{
				f[i][j+M] = true;
			}
		}
	}
	
	//遍历得到的方案,统计结果
	unsigned long long cnt = 0;//unsigned可用于防止溢出
	for(int i = 1; i <= m;i++)
		if(f[n][i+M]) cnt ++;

	cout << cnt << endl;
	return 0;
}

💓伍 - 历届真题 包子凑数【第八届】【省赛】【A/B组】

选择模型中的完全背包+掌握数论互质判断(gcd)

🌱题目描述

试题 历届真题 包子凑数【第八届】【省赛】【A组】

原题传送门

🌴解题报告

感觉这个题,读起来有点拗口,不知道小伙伴们有没有这种感觉呢…

在这里插入图片描述

通俗来说了,就是包子大叔有很多笼固定数量的包子,比如5个一笼的,9个一笼的。
有无限笼,注意无限这两个字,然后细细品读这句话小明想知道一共有多少种数目是包子大叔凑不出来的。

这句话的意思就是让咱们找到最优解,那么动态规划可以拿出来了,看到无限二字,向着完全背包的方向思考。

对于常规情况下,完全背包状态转移方程通式的获得是经过如下的闫式分析以及数学归纳之后的结果,我只是放置在这儿,不去证明了。

根据小伙伴们的需求,能理解性的记忆最好。假如时间吃紧的话也可以直接背下来先用着,以后再回头理解它的时候回会很轻松的.

完全背包

本题需要注意的第二个点是一个数学规律:

整数间若不互质,最大公因子为d,即每个整数都是d的倍数。

d > 1时,最大不能组成的整数为无穷大。因此,可以先计算出n个整数的最大公因子,若 > 1,则输出INF。
若等于1,则采用动态规划进行分析

状态表示:

状态计算:

依旧是依据最后一个不同点,完全背包中,最后一个不同点就是当前这个物品i选择的数量。可以选0个,也可以选k个。

🌵参考代码(C++版本)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 110, M = 10010;//M 是 最多一百种蒸笼,每笼包子最多100个
int v[N];
bool f[N][M];
int n;

//判断最大公约数,即欧几里得算法
int gcd(int a,int b)
{
	return b? gcd(b,a % b) : a;
}

int main()
{
	//处理输入
	cin >> n;
	for(int i  =1; i <= n;i++) cin >> v[i];
	
	//根据本题的数学背景。现在要去找到读入的数据的最大公约数
	
	int x = v[1];
	for(int i = 2; i <= n;i++) x = gcd(x, v[i]);
	
	//判断最大公约数是否大于1
	if(x > 1) printf("INF\n");
	
	//最大公约数为1,也就说,它们是互质的,那么开始DP
	else
	{
		//初始化
		f[0][0] = true;
		
		for(int i = 1; i <= n;i++)
			for(int j = 0; j <= M;j++)
			{
				if(f[i-1][j])
				{
					f[i][j]  =true;
					continue;
				}
				int k = 1;
				while(j - k * v[i] >= 0)
				{
					if(f[i-1][j-k*v[i]])
					{
						f[i][j] = true;
						break;
					}
					//跳出当前的选择,进行一下个
					k++;
				}
				
			}
		long long cnt = 0;
		//遍历所有组合,输出答案
		for(int i = 1; i <= M;i++)
			if(f[n][i] == false) cnt++;

		cout << cnt <<endl;
	}
	return 0;
}

看我画了这么几张闫式DP分析图,会不会有小伙伴潜意识中逐渐以为,做动态规划一定要画闫式DP分析图了?我当初就是这种的想的,太呆萌了😭😭😭
在这里插入图片描述


还有就是有些题目的状态转移方程比较明显,或者友友们达到一定修为了,直接可以一眼将状态怎么转移的想清楚,那么此时就不用画图了wo~

举个不好听的例子吧,很多博客讲动态规划都是斐波那契数列跳台阶。我是真的不知道这两个直接可以三分钟内想出转移方程的题为什么要被这么多博主反复赘述,可能因为比较经典…

在这里插入图片描述


💓陆 - 历届真题 调手表【第九届】【决赛】【B组】

一、记录是因为防止思维定势,闫式DP分析是帮助分析,不是说一定要画它。
二、它是有点类似于完全背包,但是和完全背包还是有点区别,比完全背包更单纯

🌱题目描述

历届真题 调手表【第九届】【决赛】【B组】
原题传送门1
原题传送门2

🌴解题报告

一、意识培养

二、温习完全背包

完全背包最淳朴的版本是这种的:

完全背包


对其进行状态表示和状态计算的分析:

状态表示:

状态计算:

完全背包

小伙们也可以看这张出上述步骤总结出来的闫式DP分析图,稍显凌乱了
完全背包

三、降服本题

闫式DP分析

状态表示:

这个题有点逗,它问题的意思是让咱们求一批最小调整方案中的最大值…离谱
在这里插入图片描述

状态计算:

要么从+1转移过来,要么从+k转移过来,那么还原回到(i-1)的状态的时候,只需要扣除相应的值,同时补上这次调整,也就是+1
写到这儿,我感觉它又像01背包了,这个题蛮可爱的。

f[i] = min(min(f[(i-1)%n], f[(i+n-k)%n]), f[i])+1;

最后遍历得到的方案,得到答案。

🌵参考代码(C++版本)

#include <iostream>
#include <algorithm>
#include <cstdio>

using namespace std;
long long n, k, j;
int f[100010];
int main()
{

  cin >> n >> k;
  //初始化,默认一次是一分钟,那么现在是多少次,就是多少分钟
  for(int i = 0; i < n; i++)  f[i] = i;

   //进行一步预处理,找到每个时间点,一次走k下,所有需要的最少次数
  for(int i = 1; i < n; i++)
  {
    j = i*k % n;
    f[j] = min(f[j], i);
  }
  
  //因为f[0]不用处理了,从2开始枚举
  for(int i = 2; i < n; i++)  f[i] = min(min(f[(i-1)%n], f[(i+n-k)%n]), f[i])+1;
  
  int maxv = 0;
  
  for(int i = 1; i < n; i++)  maxv = max(maxv, f[i]);
  
  cout << maxv << endl;
  
  return 0;
}

💓 关于动态规划的一点建议

💓 比赛将至,咱,不懂就问~

可能有小伙伴说,有些题解博客,看着太晦涩了又找不到问题的地方。
友友你可以私信问我,但是我因为这个学期加了课程,就挺吃力的。有时候因为我自己能力不够或者我直接被学校里的事儿耽搁了不能及时回复😭😭😭,所以更倾向大家请教这几位博主喔,他们都是我非常钦佩的大佬,私信他们就好喔~

在这里插入图片描述
C/C++

Pluto(算法健将,各类周赛打宝宝级别)看看Ta😊
泡泡(以大一学籍让无数大四学长自愧不如,正在刷爆洛谷)看看Ta😊
折叠的小饼干(实力派学姐,温柔耐心~)看看Ta😊
knao_s(题解绝绝子,除了详细到位,还是详细到位)看看Ta😊
永遇乐金枪鱼(一位谦虚的大佬,精准把握你题解思路中不对的地方看看Ta😊

Java

执梗(带三百人冲刺蓝桥主要负责人,讲题细心,出题尽职尽责🌹)看看Ta😊
小怂(用最朴素的for、while、if语句刷爆蓝桥云课,担心不熟悉数据结构会影响解题的小伙伴可以多请教他喔~)看看Ta😊
小羊不会飞(题解常年稳居热榜前五,高质量题解质量,为人谦虚耐心)看看Ta😊
Hydrion-Qlz(西安交大大佬,算法爱好者)看看Ta😊
小成同学(acwing师兄,刷题健将,考虑问题周全)看看Ta😊
托马斯—酷涛(Java算法爱好者,对多个方向均有涉猎)看看Ta😊
小王同学(算法博客详细,答疑热情,登上热榜第一看看Ta😊

Python

小郑(国赛强劲实力选手,热榜常客,求知欲很强)看看Ta😊
小蓝(算法思路清晰,博客题解详细)看看Ta😊
秋刀鱼(会三门语言,题解地表最详细)看看Ta😊

到这儿了,动态规划中比较简单的内容算是告一段落,解决完上一,要着手迎战三哥了

在这里插入图片描述

举报

相关推荐

0 条评论