首页 > 其他分享 >动态规划 之《从入门到入土》

动态规划 之《从入门到入土》

时间:2024-08-30 15:19:36浏览次数:11  
标签:背包 入门 int max 入土 序列 250 动态 dp

bushi

动态规划的几个模板 and 例题

背包问题

01背包

顾名思义,一个东西只有选和不选两种选择。
求体积一定的包里能放的最大质量。

	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=w[i];j--)//w[i]表示物品 i 的体积
		{
			f[j]=max(f[j],f[j-w[i]]+v[i]);//v[i]表示物品 i 的质量
            //f[j]表示 背包容量为 j 时最多能放的质量
		}
	}

为什么要逆序

首先,通过上一个问题,我们确认了我们目前一维的dp数组,保存的是确认过的最新一层的数据,即上一层的数据。
当我们计算当前层时,对于二维时的状态转移方程有
\(dp_{i,j} = max\) \(\{dp{i,j}, dp_{i-1,j-v_i} + w_i\}\);
可以看到,\(dp_{i-1,j-v_i}+ w_i\) 使用的上一层的原始数据\(dp_{i-1}\),而我们使用一维的状态转移方程时有
\(dp_{j} = max\) \(\{dp{j}, dp_{j-v_i} + w_i\}\);
当我们从小到大更新是, 因为 \(j - v_i\) 是严格小于 \(j\) 的,所以我们可以举个例子:
\(dp_3 = max\) \(\{dp_3, dp_2 + 1\}\), 因为我们是从小到大更新的,所以当更新到 \(dp_3\) 的时候,\(dp_2\)已经更新过了,已经不是上一层的 \(dp_2\)。
而当我们逆序更新时有,举例 \(dp_8 = max\) \(\{dp_8, dp_6 + 2\}\)。当更新 \(dp_8\) 时,\(dp_6\) 还没有被更新,还是上一层的数据,这样才能保证没有读入脏数据。

以上来自 AcWing

完全背包

与 01 背包的区别就是:同一个物品可以被放多次

for(int i=1;i<=n;i++)
	{
		for(int j=w[i];j<=m;j++)//这里的枚举顺序要改变一下
		{
			f[j]=max(f[j],f[j-w[i]]+v[i]);
            //f[j]表示总体积是 j 时最大价值是多少
		}
	}

枚举顺序改变的原因:

每次再使用 \(f_j\) 的时候状态就是已经更新过的

多重背包

在完全背包的基础上,每个物品是有限定数量的。
(1) 可以把每种物品看成 cnt 个物品,只不过他们的质量体积都相同,这样就能转化为 01 背包问题。
(2) 每个物品有 不选选一个选两个、………… 选 cnt 个,加一重循环就能实现。
好像两个运行时间差不多?
代码(方法 2 )

 for(int i=1;i<=n;i++)      
    for(int k=1;k<=cnt[i];k++) //物品数量,可以看作一个物品有好多份,01背包 
	{
		for(int j=m;j>=k*w[i];j--)//容量
		{
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}

例题:

这里 https://www.luogu.com.cn/training/572428

子序列问题:

最长上升子序列长度

  • 朴素做法
    \(dp_i\) 表示 以 \(i\) 点为结尾的最长上升子序列的长度。
for(int i=1;i<=n;i++)
	{
		dp[i]=1;
		for(int j=1;j<i;j++)
		{
			if(a[j]<a[i])dp[i]=max(dp[i],dp[j]+1);
		}
		maxn=max(maxn,dp[i]);
	}
  • 优化:
    如果 a_i 大于原序列末尾,就可以把他直接加上;
    如果小于原序列末尾,二分查找到此时序列中第一个小于等于它的值,替换掉。

理由:
如果y在末尾,由于y < a[i],所以y后面能接的不如a[i]多,y让位给a[i]可以让序列更长
如果y不在末尾,那y有生之年都不会再被用到了,直接踹了y就行,y咋样,who care?

p[0]=-1;
for(int i=1;i<=n;i++) //求 最长上升子序列的长度 
{
	if(a[i]>p[cnt])p[++cnt]=a[i];//直接加在后面
	else{         // 查找第一个 >= a[i] 的元素的位置 
		int left=1,right=cnt;
		while(left<right)
		{
			int mid=left+right>>1;
			if(p[mid]>=a[i]) right=mid;
			else left=mid+1; 
		}
		p[left]=a[i]; //替换掉
	}
} 

其他什么上升下降都差不多,就不再说了

最少的不上升子序列的个数

Dilworth 定理

对偏序集 $ \langle A, \le \rangle$,设 \(A\) 中最长链的长度是 \(n\),则将 \(A\) 中元素分成不相交的反链,反链个数至少是 \(n\)。

人话翻译:最少的不上升子序列的个数就是最长上升子序列的长度
具体证明我不会请参考 https://www.luogu.com.cn/article/znt20wu4

for(int i=1;i<=n;i++)//求最长不下降子序列 
	{
		if(a[i]<=d[cnt]) d[++cnt]=a[i];
		else{
			int left=1,right=cnt;
			while(left<right)//查找第一个 < a[i] 的元素的位置 (因为等于的时候不需要替换)
			{
				int mid=left+right>>1;
				if(d[mid]<a[i])right=mid;
				else left=mid+1;
			}
			d[left]=a[i];
		}
	}

区间DP

性质

  • 合并:将两个或者多个部分进行整合,当然也可以反过来;
  • 特征:能将问题分解成两两合并的形式;
  • 求解:对整个问题设最优解,枚举合并点,将问题分解成左右两个部分,最后合并两个部分的最优值得到原问题的最优值。

定义:

定义 \(f_{i,j}\) 表示将下标位置 i 到 j 的所有元素合并能获得的价值的最大值,那么 \(f_{i,j} = max\) \(\{f_{i,k}+f_{k+1,j}+cost\}\),cost 为将这两组元素合并的价值。

以上来自 OIWIKI

例题:

NOI1995 石子合并

  • 观察到是环,首先 ctrl + c,ctrl + v 复制一份接在后面
  • 最普通的算法O(n^3):224ms
    其中,\(dp_{i,j}\) 代表 \(i\) 到 \(j\) 堆的最优值,\(sum_i\) 代表第 \(1\) 堆到第 \(i\) 堆的数目总和。有:\(dp_{i,j}=min\) \(\{dp_{i,j},dp{i,k}+dp_{k+1,j}\}\) \(+sum_j-sum_{i-1}\)。
  • 代码:17ms
#include<bits/stdc++.h>

using namespace std;

int n;
int a[250];
int sum[250];
int dp[250][250];
int dp2[250][250];
int minn=INT_MAX;
int maxn=INT_MIN;

signed main()
{
	memset(dp2,0x3f3f3f,sizeof(dp2));
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		a[n+i]=a[i];//复制一份接在后面 
	}
	int N=2*n;
	for(int i=1;i<=N;i++)
	{
		sum[i]=sum[i-1]+a[i];
		dp2[i][i]=0;
	}
	for(int k=2;k<=n;k++) //区间长度
	{
		for(int i=1;i<=N-k+1;i++) //枚举左端点 
		{
			int j=i+k-1;
			for(int p=i;p<j;p++) //枚举中间的序号
			{
				dp[i][j]=max(dp[i][j],dp[i][p]+dp[p+1][j]+sum[j]-sum[i-1]); //最大值 
				dp2[i][j]=min(dp2[i][j],dp2[i][p]+dp2[p+1][j]+sum[j]-sum[i-1]); //最小值 
			 } 
		}
	}
	for(int i=1;i<=n;i++)//开始点 
	{
		int p=i+n-1;//结束点
		maxn=max(maxn,dp[i][p]);
		minn=min(minn,dp2[i][p]);
	}
	cout<<minn<<endl;
	cout<<maxn<<endl;
	return 0;
 } 

练习题:

直接去洛谷搜标签

完结撒花!!!

csp_j 应该也就只能考这些了吧 qwq

标签:背包,入门,int,max,入土,序列,250,动态,dp
From: https://www.cnblogs.com/lazy-ZJY0307/p/18385482

相关文章

  • CSS、JS之动态展开式菜单
    效果演示实现了一个可展开菜单按钮的效果,点击按钮会弹出一个菜单列表,菜单列表中包含多个选项。按钮的样式为一个圆形背景,中间有三条横线,表示可以展开。当按钮被点击后,三条横线会变成一个叉号,表示可以收起。菜单列表的样式为一个白色背景,四周有阴影,包含多个选项,每个选项都有......
  • Pinia入门(快速上手)
    定义一个Store 在深入了解核心概念之前,我们需要知道Store是使用 defineStore() 定义的,并且它需要一个唯一名称,作为第一个参数传递:import{defineStore}from'pinia'//useStore可以是useUser、useCart之类的任何东西//第一个参数是应用程序中store的唯一i......
  • HTTP协议入门
    HTTP协议入门参考:http://www.ruanyifeng.com/blog/2016/08/http.html      一、HTTP/0.9HTTP是基于TCP/IP协议的应用层协议。它不涉及数据包传输,主要规定了客户端和服务器之间的通信格式,默认使用80端口。最早版本是1991年发布的0.9版。该版本及其简单,只有一个命......
  • 第九章 动态规划Part12
    目录任务115.不同的子序列思路583.两个字符串的删除操作思路72.编辑距离思路任务115.不同的子序列给你两个字符串s和t,统计并返回在s的子序列中t出现的个数,结果需要对10^9+7取模。思路dp[i][j]表示s[0:i)中出现以t[0,j)的次数每次拓展一个字符,求次数,直到拓......
  • JS动态引入模块
    这是静态引入,importxxfrom‘xxx’;这是动态引入,import('xxx')动态引入是一个异步操作,即它会返回一个Promise对象,因此我们可以捕获引入失败的异常。具体运用场景:路由由后端动态生成,前端根据获取到的路由动态生成菜单,并根据对应路由去找到对应的组件进行跳转。譬如路由为/hom......
  • 实现一个动态评论系统:Vue3与后端API交互
    在当今的开发环境中,评论系统是多种应用中不可或缺的一部分,本文将带您深入了解如何使用Vue3实现一个动态评论系统,并与后端API进行交互。我们将重点使用Vue3的compositionAPI(setup语法糖)来构建这个系统。需求概述在构建动态评论系统时,我们需要实现以下功能:获取评论......
  • Python编程实战营:四款实用小项目助你快速入门,从零开始打造你的个人项目集!
    踏入编程世界的门槛,总是伴随着既兴奋又忐忑的心情。作为Python的新手,你是否渴望通过实际项目来巩固知识、提升技能?本篇文章将引领你踏上一段从理论到实践的精彩旅程,通过四个精心设计的项目,让你在趣味与挑战中快速成长。项目一:简易文本编辑器首先,我们将从基础出发,动手打造一......
  • LLaMA-Factory微调入门个人重制版
    LLaMA-Factory微调入门个人重制版说明:首次发表日期:2024-08-30LLaMA-Factory官方Github仓库:https://github.com/hiyouga/LLaMA-Factory关于本文是对LLaMA-Factory入门教程https://zhuanlan.zhihu.com/p/695287607的个人重制版,记录一下学习过程,省略掉了很多文字部分,建议......
  • openGauss-动态数据脱敏机制
    openGauss-动态数据脱敏机制可获得性本特性自openGauss1.1.0版本开始引入。特性简介数据脱敏是行之有效的数据库隐私保护方案之一,可以在一定程度上限制非授权用户对隐私数据的窥探。动态数据脱敏机制是一种通过定制化制定脱敏策略从而实现对隐私数据保护的一种技术,可以有效......
  • Pearson相关性分析 —— 入门案例
    简介Pearson相关系数Pearson相关系数是一种统计方法,用于衡量两个连续变量之间的线性相关程度。Pearson相关系数的值范围从-1到1,其中:接近1的值表示两个变量之间存在强正线性关系。接近-1的值表示两个变量之间存在强负线性关系。接近0的值表示两个变量之间没有线性关系。Pea......