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

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数组,保存的是确认过的最新一层的数据,即上一层的数据。
当我们计算当前层时,对于二维时的状态转移方程有
dpi,j=max {dpij,dpi1jvi+wi};
可以看到,dpi1,jvi+wi 使用的上一层的原始数据dpi1,而我们使用一维的状态转移方程时有
dpj=max {dpj,dpjvi+wi};
当我们从小到大更新是, 因为 jvi 是严格小于 j 的,所以我们可以举个例子:
dp3=max {dp3,dp2+1}, 因为我们是从小到大更新的,所以当更新到 dp3 的时候,dp2已经更新过了,已经不是上一层的 dp2
而当我们逆序更新时有,举例 dp8=max {dp8,dp6+2}。当更新 dp8 时,dp6 还没有被更新,还是上一层的数据,这样才能保证没有读入脏数据。

以上来自 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 时最大价值是多少
}
}

枚举顺序改变的原因:

每次再使用 fj 的时候状态就是已经更新过的

多重背包

在完全背包的基础上,每个物品是有限定数量的。
(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]);
}
}

分组背包

思路:每一组物品有 cnti+1 种决策:
不选、选第一个、选第二个、…… 选第 cnt 个。(01背包)

  • 代码
for(int i=1;i<=n;i++)//枚举每一组
{
int cnt;
cin>>cnt;
for(int j=1;j<=cnt;j++)
{
cin>>w[j]>>v[j];//输入
}
for(int j=m;j>=0;j--)//枚举背包容积
{
for(int k=1;k<=cnt;k++)//枚举每一种决策
{
if(j>=w[k]) f[j]=max(f[j],f[j-w[k]]+v[k]);
}
}
}
cout<<f[m]<<endl;

例题:

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

子序列问题:

最长上升子序列长度

  • 朴素做法
    dpi 表示 以 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 定理

对偏序集 A,,设 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

性质

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

定义:

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

以上来自 OIWIKI

例题:

NOI1995 石子合并

  • 观察到是环,首先 ctrl + c,ctrl + v 复制一份接在后面
  • 最普通的算法O(n^3):224ms
    其中,dpi,j 代表 ij 堆的最优值,sumi 代表第 1 堆到第 i 堆的数目总和。有:dpi,j=min {dpi,j,dpi,k+dpk+1,j} +sumjsumi1
  • 代码: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;
}

练习题:

直接去洛谷搜标签

树形 DP

有依赖的背包问题

意思:给你一棵树,选取一个节点就必须选取他的父亲节点,求物品总体积不超过背包容量,且最大的总价值。

思路:

  • 定义 fi,j 表示以 i 为根的子树,总共体积不超过 j 的最大价值
  • 初始化要注意,0<j<m 时并不是所有的 f 都为0,
    因为不选就整颗子树就都不能选了,
    所以计算前只有fi,0=0, fi,wi=vi;
    其他的都置为无穷小就行了
  • 然后我们还需要一个 gi,j 来表示: 当前这个根节点的到第 i 个儿子时,用 j 的空间所获得的最大价值
    然后在遍历子节点的时候更新一下 g 数组
    具体就是gi,j=gi1,jk+fy,k (y 是第 i 个儿子的编号)

优化:

建议看: http://note.youdao.com/noteshare?id=07619acf27a64381650dbb9dc2000f68&sub=4FCC06D686104EA19BFFBF85E614343D

完结撒花!!!

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

posted @   lazy_ZJY  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示