DP从入门到放弃

数字三角形

给出下面的数字金字塔,求一条从最高点到底部任意处结束的路径,使得这条路径经过的数字的和最大。每一步可以向左下或者右下走
在这里插入图片描述
输入金字塔,输出最大的和

经典DP,当年的IOI赛题

方程: f [ x ] [ y ] = m a x ( f [ x + 1 ] [ y ] , f [ x + 1 ] [ y + 1 ] ) + a [ x ] [ y ] f[x][y]=max(f[x+1][y],f[x+1][y+1])+a[x][y] f[x][y]=max(f[x+1][y],f[x+1][y+1])+a[x][y]

[NOIP1996 提高组] 挖地雷

题目描述

在一个地图上有N个地窖(N≤20),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。

输入格式:
有若干行。第1行只有一个数字,表示地窖的个数N。第2行有N个数,分别表示每个地窖中的地雷个数。第3行至第N+1行表示地窖之间的连接情况
第3行有n−1个数(0或1),表示第一个地窖至第2个、第3个、…、第n个地窖有否路径连接。
第4行有n−2个数,表示第二个地窖至第3个、第4个、…、第n个地窖有否路径连接。
… …
第n+1行有1个数,表示第n−1个地窖至第n个地窖有否路径连接。(为0表示没有路径,为1表示有路径)。
输出格式:
第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。
第二行只有一个数,表示能挖到的最多地雷数。

线性DP,方程: d p [ i ] dp[i] dp[i]指以i结尾的最长方案数,则

d p [ i ] = d p [ j ] + w [ i ] , j ∈ { 1 , 2 , 3 , 4... n } dp[i]=dp[j]+w[i],j\in \{1,2,3,4...n\} dp[i]=dp[j]+w[i],j{1,2,3,4...n}

最大食物链

输入格式
第一行,两个正整数 n、m,表示生物种类 n和吃与被吃的关系数 m。
接下来 m 行,每行两个正整数,表示被吃的生物A和吃A的生物B。
输出格式
一行一个整数,为最大食物链数量模上 80112002的结果。

树形DP:设f[u]为以u结尾的食物链的长度

方程: f [ u ] = ∑ f [ v ] f[u]=\sum f[v] f[u]=f[v]

[NOIP2005 普及组] 采药

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?

01背包,方程: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − v [ i ] ] + w [ i ] , f [ i − 1 ] [ j ] ) f[i][j]=max(f[i-1][j-v[i]]+w[i],f[i-1][j]) f[i][j]=max(f[i1][jv[i]]+w[i],f[i1][j])

P1616 疯狂的采药

LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是 LiYuxiang,你能完成这个任务吗?
此题和原题的不同点:
A:每种草药可以无限制地疯狂采摘。
B:药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

完全背包,方程:

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] + w [ i ] , f [ i − 1 ] [ j − 2 v [ i ] ] + 2 w [ i ] . . ) f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2v[i]]+2w[i]..) f[i][j]=max(f[i1][j],f[i1][jv[i]]+w[i],f[i1][j2v[i]]+2w[i]..)

f [ i ] [ j − v [ i ] ] = m a x ( f [ i − 1 ] [ j − v [ i ] ] , f [ i − 1 ] [ j − 2 v [ i ] ] + w [ i ] , f [ i − 1 ] [ j − 3 v [ i ] ] + 2 w [ i ] . . ) f[i][j-v[i]]=max(f[i-1][j-v[i]],f[i-1][j-2v[i]]+w[i],f[i-1][j-3v[i]]+2w[i]..) f[i][jv[i]]=max(f[i1][jv[i]],f[i1][j2v[i]]+w[i],f[i1][j3v[i]]+2w[i]..)

故综合上式:

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − v [ i ] ] + w [ i ] ) f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]) f[i][j]=max(f[i1][j],f[i][jv[i]]+w[i])

5 倍经验日

现在 absi2011 拿出了 x 个迷你装药物(嗑药打人可耻…),准备开始与那些人打了。
由于迷你装药物每个只能用一次,所以 absi2011 要谨慎的使用这些药。悲剧的是,用药量没达到最少打败该人所需的属性药药量,则打这个人必输。例如他用 2 个药去打别人,别人却表明 3 个药才能打过,那么相当于你输了并且这两个属性药浪费了。
现在有 n 个好友,给定失败时可获得的经验、胜利时可获得的经验,打败他至少需要的药量。
要求求出最大经验 s,输出 5s。

01背包,方程:

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] + l o s e [ i ] , f [ i − 1 ] [ j − u s e [ i ] ] + w i n [ i ] )   ( j − u s e [ i ] > = 0 ) f[i][j]=max(f[i-1][j]+lose[i],f[i-1][j-use[i]]+win[i])\ (j-use[i]>=0) f[i][j]=max(f[i1][j]+lose[i],f[i1][juse[i]]+win[i]) (juse[i]>=0)
f [ i ] [ j ] = f [ i − 1 ] [ j ] + l o s e [ i ]   ( j − u s e [ i ] < 0 ) f[i][j]=f[i-1][j]+lose[i]\ (j-use[i]<0) f[i][j]=f[i1][j]+lose[i] (juse[i]<0)

[NOIP1999 普及组] 导弹拦截

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

第一问求一个最长不上升子序列,第二问求一个最长上升子序列

以最长不上升子序列为例,设f[i]为以第i个数为结尾的最长不上升子序列长度,则: f [ i ] = f [ j ] + 1 f[i]=f[j]+1 f[i]=f[j]+1,简单递推可得到n方的算法

但是有nlogn的算法:https://www.cnblogs.com/itlqs/p/5743114.html

在这里插入图片描述
也可以用数据结构优化也可以达到nlogn的复杂度:

https://blog.csdn.net/weixin_43907802/article/details/96433490

int x,cnt=0;clean(f,0);clean(f1,0x3f);
	while(scanf("%d",&x)!=EOF)
		a[++cnt] = x;
	f[1]=a[1];f1[1]=a[1];
	int loc,len=1,loc1,len1=1;
	loop(i,2,cnt){
		loc = upper_bound(f+1,f+len+1,a[i],greater <int> ()) - f;
		loc1 = lower_bound(f1+1,f1+len1+1,a[i]) - f1;
		f[loc] = max(f[loc],a[i]);
		f1[loc1] = min(f1[loc1],a[i]);
		len = max(len,loc);
		len1 = max(len1,loc1);
	}
	printf("%d\n%d",len,len1);

要注意upper_bound和lower_bound的区别

这个题其实就涉及一堆问题:最长下降子序列,最长不上升子序列,最长不下降子序列,最长上升子序列。其实都是一样的。

LCS(最长公共子序列)

给出 1,2,…,n1,2,\ldots,n1,2,…,n 的两个排列 P1P_1P1​ 和 P2P_2P2​ ,求它们的最长公共子序列。

LCS,设f[i][j]为A串以第i个结尾,B串以第j个结尾的最长LCS,方程:

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − 1 ] + 1 , f [ i ] [ j ] )   A [ i ] = B [ j ] f[i][j]=max(f[i-1][j-1]+1,f[i][j]) \ A[i]=B[j] f[i][j]=max(f[i1][j1]+1,f[i][j]) A[i]=B[j]

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] )   A [ i ] ≠ B [ j ] f[i][j]=max(f[i-1][j],f[i][j-1]) \ A[i]\neq B[j] f[i][j]=max(f[i1][j],f[i][j1]) A[i]=B[j]

尼克的任务

尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。
尼克的一个工作日为 n 分钟,从第 1 分钟开始到第 n 分钟结束。当尼克到达单位后他就开始干活,公司一共有 k 个任务需要完成。如果在同一时刻有多个任务需要完成,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第 p 分钟开始,持续时间为 t 分钟,则该任务将在第 (p+t−1) 分钟结束。
写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。

设f[i]为到达第i个时刻的最大空暇时间,则

(本时刻无任务) f [ i ] = f [ i + 1 ] + 1 f[i]=f[i+1]+1 f[i]=f[i+1]+1

(本时刻有任务) f [ i ] = m a x ( f [ i ] , f [ i + a [ s u m ] ) f[i]=max(f[i],f[i+a[sum]) f[i]=max(f[i],f[i+a[sum])

[NOIP2003 提高组] 加分二叉树

设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n)\,其中数字 1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di​,tree及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:
subtree 的左子树的加分 × subtree 的右子树的加分 + subtree 的根的分数。
若某个子树为空,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为 (1,2,3,…,n)且加分最高的二叉树 tree。要求输出:
tree 的最高加分。
tree 的前序遍历。

复习一下前中后序遍历先:

在这里插入图片描述
因此本题中,根据前序遍历的特性可设f[l][r]为区间[l,r]构成的子树的最大分数,有方程:

f [ l ] [ r ] = m a x ( f [ l ] [ k − 1 ] ∗ f [ k + 1 ] [ r ] + f [ k ] [ k ] ) , f [ k ] [ k ] = S c o r e [ k ] f[l][r]=max(f[l][k-1]*f[k+1][r]+f[k][k]),f[k][k]=Score[k] f[l][r]=max(f[l][k1]f[k+1][r]+f[k][k])f[k][k]=Score[k]

编辑距离

设 A 和 B 是两个字符串。我们要用最少的字符操作次数,将字符串 A 转换为字符串 B。这里所说的字符操作共有三种:
删除一个字符;
插入一个字符;
将一个字符改为另一个字符。
A,B 均只包含小写字母。

方程:设 f [ i ] [ j ] f[i][j] f[i][j]为A串的前i个字符变为B串的前j个字符的最少操作数

增加: f [ i ] [ j ] = f [ i − 1 ] [ j ] + 1 f[i][j]=f[i-1][j]+1 f[i][j]=f[i1][j]+1
删除: f [ i ] [ j ] = f [ i ] [ j − 1 ] + 1 f[i][j]=f[i][j-1]+1 f[i][j]=f[i][j1]+1
修改: f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + ( A [ i ] = = B [ j ] ) ? 0 : 1 f[i][j]=f[i-1][j-1]+(A[i]==B[j])?0:1 f[i][j]=f[i1][j1]+(A[i]==B[j])?0:1

综上:

f [ i ] [ j ] = m i n ( f [ i − 1 ] [ j ] + 1 , f [ i ] [ j − 1 ] + 1 , f [ i − 1 ] [ j − 1 ] + ( A [ i ] = = B [ j ] ) ? 0 : 1 ) f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1,f[i-1][j-1]+(A[i]==B[j])?0:1) f[i][j]=min(f[i1][j]+1,f[i][j1]+1,f[i1][j1]+(A[i]==B[j])?0:1)

这道题想了挺久。无论使用闫氏DP法还是用之前欧阳讲的“最后一步倒推”,关键都是在于理解最后一步的含义。这里的最后一步,无非就三种情况:

  • 如果是增加来的,那么增加之前一定是 f [ i ] [ j − 1 ] f[i][j-1] f[i][j1],因为A的前i和B的前j-1已经一致了,那么要让A的前i和B的前j一致,则必须要在 f [ i ] [ j − 1 ] f[i][j-1] f[i][j1]中所代表的A串的末尾再加上B[j]这个字符
  • 如果是删除来的,那么删除之前一定是 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]
  • 如果是修改来的,那么修改之前一定是 f [ i − 1 ] [ j − 1 ] f[i-1][j-1] f[i1][j1]

因此以后在思考DP的时候回溯的时候,可以问问自己:回溯之前是什么状态?

luogu P4933 大师

ljt12138 首先建了 个特斯拉电磁塔,这些电塔排成一排,从左到右依次标号为 1 到 n ,第 i 个电塔的高度为 h[i] 。
建筑大师需要从中选出一些电塔,然后这些电塔就会缩到地下去。这时候,如果留在地上的电塔的高度,从左向右构成了一个等差数列,那么这个选择方案就会被认为是美观的。
建筑大师需要求出,一共有多少种美观的选择方案,答案模 998244353。
注意,如果地上只留了一个或者两个电塔,那么这种方案也是美观的。地上没有电塔的方案被认为是不美观的。
同时也要注意,等差数列的公差也可以为负数。

线性DP:设 f [ i ] [ j ] f[i][j] f[i][j]为以i为结尾的,公差为j的方案数,则有

f [ i ] [ j ] = ∑ ( f [ k ] [ j ] + 1 ) , ( h [ k ] − h [ i ] = j ) f[i][j]=\sum (f[k][j]+1),(h[k]-h[i]=j) f[i][j]=(f[k][j]+1),(h[k]h[i]=j)

因此不难想到一个 O ( N 2 V ) O(N^2V) O(N2V)的算法,就是先找j,再找i,再找k

但是其实可以发现,当i和k确定了后,j就确定了,因此可以直接枚举i和k,就可以转移,复杂度为 O ( N 2 ) O(N^2) O(N2)

loop(i,1,n){
	res++;
	loop(j,1,i-1){
		f[i][h[j]-h[i]+v] = (f[i][h[j]-h[i]+v] + f[j][h[j]-h[i]+v] + 1)%mod;//yuan lai de fang cheng xie cuo le
		res = (res + f[j][h[j]-h[i]+v] + 1)%mod;
	}
}
printf("%lld",res);

[NOIP2012 普及组] 摆花

小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共 m盆。通过调查顾客的喜好,小明列出了顾客最喜欢的 n 种花,从 1 到 n 标号。为了在门口展出更多种花,规定第 i 种花不能超过 ai 盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
试编程计算,一共有多少种不同的摆花方案。

线性DP:设f[i][j]为前i种花放入前j个位置中,则方程为

f [ i ] [ j ] = ∑ k = 0 m i n ( j , a [ i ] ) f [ i − 1 ] [ j − k ] f[i][j]=\sum_{k=0}^{min(j,a[i])}f[i-1][j-k] f[i][j]=k=0min(j,a[i])f[i1][jk]

但是一个很迷的事情就是,一种花可不可以完全不放。按理来讲是不行的。但是代码里面又不一样了。

loop(i,1,a[1])f[1][i]=1;
loop(i,1,n)f[i][0]=1;
loop(i,2,n){
	loop(j,1,m){
		loop(k,0,min(j,a[i])){//如果必须要放的话那应该从1开始遍历才对
			f[i][j] = (f[i-1][j-k] + f[i][j])%mod;
		}
		
	}
}printf("%lld\n",f[n][m]%mod);

木棍加工

一堆木头棍子共有n根,每根棍子的长度和宽度都是已知的。棍子可以被一台机器一个接一个地加工。机器处理一根棍子之前需要准备时间。准备时间是这样定义的:
第一根棍子的准备时间为1分钟;
如果刚处理完长度为L,宽度为W的棍子,那么如果下一个棍子长度为Li,宽度为Wi,并且满足L>=Li,W>=Wi,这个棍子就不需要准备时间,否则需要1分钟的准备时间;
计算处理完n根棍子所需要的最短准备时间。比如,你有5根棍子,长度和宽度分别为(4, 9),(5, 2),(2, 1),(3, 5),(1, 4),最短准备时间为2(按(4, 9)、(3, 5)、(1, 4)、(5, 2)、(2, 1)的次序进行加工)。

LIS问题的小修改。

先对宽度降序排序,如果宽度一样则按照长度降序排序,于是就有前面的木棍宽度一定大于后面的木棍,而前面的木棍宽度可能小于后面的木棍。

此时的最优解一定求不下降子序列的最少个数。

而根据Dilworth定理,不下降子序列的最少个数就是最长上升子序列的长度

因此就求最长上升子序列的长度就可以了。

[NOIP2004 提高组] 合唱队形

n 位同学站成一排,音乐老师要请其中的 n−k 位同学出列,使得剩下的 k 位同学排成合唱队形。
合唱队形是指这样的一种队形:设 k 位同学从左到右依次编号为 1,2, … ,k,他们的身高分别为 t1,t2​, … ,tk,则他们的身高满足t1<⋯<ti​>ti+1​> … >tk(1≤i≤k)
你的任务是,已知所有 n 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

求两个东西:求从左到右的LIS和从右到左的LIS

然后对每一个位置i,可以求出以i为最高点需要出列的人数。然后就最后取个min就可以了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AndrewMe8211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值