2.3 动态规划1

一、基本概念

1.状态

状态定义:完全表示当前状态,无冗余信息

性质:最优子结构,无后效性

状态转移:利用前面的状态通过某一转移决策推出当前状态的过程

状态转移方程:转移决策

2.实现方式

(1)记忆化搜索

主要思想:在函数递归时如果出现了已经得到过的函数值,不妨开辟新数组把它存储下来,再次传入相同参数时,就可以直接获取数组中的值,减少不必要的时间消耗(用空间换时间)

使用条件:

  1. 函数返回结果只与参数有关
  2. 同样参数的函数会被多次调用

一般使用函数的递归调用进行求解

(2)递推

主要思想:把复杂问题转化为简单问题的多次重复,一般要对问题推导递推关系式求解

一般使用数组进行求解

3.典例

(1)数字三角形 ( I O I 1994 ) (IOI1994) (IOI1994)

定义 f ( i , j ) f(i,j) f(i,j)为走到 ( i , j ) (i,j) (i,j)的最大值

状态转移方程:

f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − 1 ) ) + a ( i , j ) f(i,j)=max(f(i-1,j),f(i-1,j-1))+a(i,j) f(i,j)=max(f(i1,j),f(i1,j1))+a(i,j)

(2)数字三角形 拓展 1 1 1

在原题基础上,变为求和 m o d   100 mod\space 100 mod 100的最大值

若按原来的状态定义,无法满足最优子结构

思考:如何将 m o d   100 mod\space 100 mod 100定义在状态中?

定义 f ( i , j , k ) f(i,j,k) f(i,j,k)表示从起点走到 ( i , j ) (i,j) (i,j),能否满足结果为 k k k,状态转移方程:

f ( i , j , k ) = f ( i − 1 , j , k − a ( i , j ) ) ∣ f ( i − 1 , j − 1 , k − a ( i , j ) ) f(i,j,k)=f(i-1,j,k-a(i,j))|f(i-1,j-1,k-a(i,j)) f(i,j,k)=f(i1,j,ka(i,j))f(i1,j1,ka(i,j))

注意这里 k − a ( i , j ) k-a(i,j) ka(i,j)的计算结果要对 100 100 100取模

(3)数字三角形 拓展 2 2 2

在原题基础上,必须经过某个点,求最大值

两种做法:

  1. 将经过点所在行的其他点权值设为 − ∞ -\infty

  2. 从经过点分别往上下两个方向进行DP,得到的结果相加

(4)最长上升子序列 ( L I S ) (LIS) (LIS)

定义 f ( i ) f(i) f(i)表示以 i i i结尾的 L I S LIS LIS长度

状态转移方程:

f ( i ) = { f ( i − 1 ) + 1 , a ( i ) > a ( i − 1 ) m a x ( f ( i − 1 ) , f ( j ) + 1 ) , a ( i ) ≤ a ( i − 1 ) , j < i , a ( j ) < a ( i ) f(i)= \begin{cases} f(i-1)+1,&a(i)>a(i-1)\\ max(f(i-1),f(j)+1),&a(i)\leq a(i-1),j<i,a(j)<a(i) \end{cases} f(i)={f(i1)+1,max(f(i1),f(j)+1),a(i)>a(i1)a(i)a(i1),j<i,a(j)<a(i)

(5)最长公共子序列 ( L C S ) (LCS) (LCS)

定义 f ( i , j ) f(i,j) f(i,j)表示 a 1 , a 2 , ⋯   , a i a_1,a_2,\cdots,a_i a1,a2,,ai b 1 , b 2 , ⋯   , b j b_1,b_2,\cdots,b_j b1,b2,,bj L C S LCS LCS长度

状态转移方程:

f ( i , j ) = { f ( i − 1 , j − 1 ) + 1 , a ( i ) = a ( j ) m a x ( f ( i − 1 , j ) , f ( i , j − 1 ) ) , a ( i ) ≠ a ( j ) f(i,j)= \begin{cases} f(i-1,j-1)+1,&a(i)=a(j)\\ max(f(i-1,j),f(i,j-1)),&a(i)\neq a(j) \end{cases} f(i,j)={f(i1,j1)+1,max(f(i1,j),f(i,j1)),a(i)=a(j)a(i)=a(j)

二、区间 D P DP DP

1.朴素(线形)

例题:石子合并

  • 在一条公路上摆放 n n n堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 2 2 2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分,求将 n n n堆石子合并为 1 1 1堆后,得分的最小值。

定义 f ( l , r ) f(l,r) f(l,r)表示从 l l l合并到 r r r的最小代价

首先预处理出前缀和数组 s u m sum sum

对于区间 ( l , r ) (l,r) (l,r),只有可能是由两个区间 ( l , k ) (l,k) (l,k) ( k + 1 , r ) (k+1,r) (k+1,r)合并而来

状态转移方程:

f ( l , r ) = m i n { f ( l , k ) + f ( k + 1 , r ) + s u m [ r ] − s u m [ l − 1 ] } , l ≤ k < r f(l,r)=min\{f(l,k)+f(k+1,r)+sum[r]-sum[l-1]\},l\leq k<r f(l,r)=min{f(l,k)+f(k+1,r)+sum[r]sum[l1]},lk<r

注意:在 D P DP DP时,需要先枚举区间长度,再根据每一个 l l l,求出对应的 r r r

2.环形

在原题基础上,结构由线形变为环形(即第 1 1 1堆能与第 n n n堆合并)

首先通过分析,不难发现进行 n − 1 n-1 n1次合并后就能合并为一堆,而环形中间存在 n n n个合并位置,我们进行如下操作:

5 5 5个数 a , b , c , d , e a,b,c,d,e a,b,c,d,e为例,首先将其加倍,变为 a , b , c , d , e , a , b , c , d , e a,b,c,d,e,a,b,c,d,e a,b,c,d,e,a,b,c,d,e

对于每种合并方式,不难发现就是如下五种情况中的朴素做法的最小值

a b c d e a b c d e 1. a b c d e 2. b c d e a 3. c d e a b 4. d e a b c 5. e a b c d \begin{matrix} &a&b&c&d&e&a&b&c&d&e&\\ 1.&a&b&c&d&e&&&&&&\\ 2.&&b&c&d&e&a&&&&&\\ 3.&&&c&d&e&a&b&&&&\\ 4.&&&&d&e&a&b&c&&&\\ 5.&&&&&e&a&b&c&d&&\\ \end{matrix} 1.2.3.4.5.aabbbccccdddddeeeeeeaaaaabbbbcccdde

三、背包 D P DP DP

n n n件物品和一个容量为 m m m的背包。物品 i i i的体积是 v i v_i vi,价值是 w i w_i wi。求解将哪些物品装入背包可使价值总和最大

1. 01 01 01背包问题

01 01 01背包问题特点:每种物品仅有一件

状态表示:定义 f ( i , j ) f(i,j) f(i,j)表示只从前 i i i个物品中选择,总体积不超过 j j j的最大价值,最终答案为 f ( n , m ) f(n,m) f(n,m)

状态转移: f ( i , j ) f(i,j) f(i,j)可以分为不选物品 i i i和选物品 i i i两种情况

状态转移方程:
f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i ) f(i,j)=max(f(i-1,j),f(i-1,j-v_i)+w_i) f(i,j)=max(f(i1,j),f(i1,jvi)+wi)

int f[N][N];
for(int i=1;i<=n;i++){
	for(int j=1;j<=m;j++){
		f[i][j]=f[i-1][j];
		if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
	}
}

优化:从二维变成一维

注意:这里 j j j从大到小迭代,保证 f ( j ) f(j) f(j) f ( j − v i ) f(j-v_i) f(jvi)改变前改变,从而使当前的f[j-v[i]]表示i-1时的状态

int f[N];
for(int i=1;i<=n;i++)
	for(int j=m;j>=v[i];j++)
		f[j]=max(f[j],f[j-v[i]]+w[i]);

2.完全背包问题

完全背包问题特点:每种物品有无数件

思路:类比 01 01 01背包

状态表示:定义 f ( i , j ) f(i,j) f(i,j)表示只从前 i i i个物品中选择,总体积不超过 j j j的最大价值

状态转移: f ( i , j ) f(i,j) f(i,j)可以分为不选物品 i i i和选 1 1 1 ~ ⌊ j v i ⌋ \lfloor\frac{j}{v_i}\rfloor vij个物品 i i i两种情况

等价变形: f ( i , j ) f(i,j) f(i,j)可以分为选 0 0 0 ~ ⌊ j v i ⌋ \lfloor\frac{j}{v_i}\rfloor vij个物品 i i i的情况

状态转移方程:
f ( i , j ) = m a x ( f ( i , j ) , f ( i − 1 , j − k v i ) + k w i ) ( k ∈ [ 0 , ⌊ j v i ⌋ ] , k ∈ Z ) f(i,j)=max(f(i,j),f(i-1,j-kv_i)+kw_i)\quad(k\in [0,\lfloor\frac{j}{v_i}\rfloor],k\in Z) f(i,j)=max(f(i,j),f(i1,jkvi)+kwi)(k[0,vij],kZ)

int f[N][N];
for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
		for(int k=0;k*v[i]<=j;k++)
			f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);

优化:对于 f ( i , j ) f(i,j) f(i,j) f ( i , j − v i ) f(i,j-v_i) f(i,jvi)而言:

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,\cdots) f(i,j)=max(f(i1,j),f(i1,jvi)+wi,f(i1,j2vi)+2wi,)

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,\cdots) f(i,jvi)=max(f(i1,jvi),f(i1,j2vi)+wi,f(i1,j3vi)+2wi,)

我们惊奇的发现: f ( i , j − v i ) f(i,j-v_i) f(i,jvi)的表达式与 f ( i , j ) f(i,j) f(i,j)的表达式除第一项以外只差 w i w_i wi

于是将状态转移方程改写为:
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,jvi)+wi)

int f[N][N];
for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++){
		f[i][j]=f[i-1][j];
		if(j>=w[i]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
	}

再优化:从二维变成一维

注意:这里 j j j不需要从大到小迭代,因为要让 f ( j − v i ) f(j-v_i) f(jvi) f ( j ) f(j) f(j)改变前改变,从而保证物品 i i i可以购买多件

int f[N];
for(int i=1;i<=n;i++)
	for(int j=v[i];j<=m;j++)
		f[j]=max(f[j],f[j-v[i]]+w[i]);

3.多重背包问题

多重背包问题特点:每种物品有件数限制

额外定义物品 i i i s i s_i si

状态表示:定义 f ( i , j ) f(i,j) f(i,j)表示只从前 i i i个物品中选择,总体积不超过 j j j的最大价值

状态转移: f ( i , j ) f(i,j) f(i,j)可以分为选 0 0 0 ~ m i n ( s i , ⌊ j v i ⌋ ) min(s_i,\lfloor\frac{j}{v_i}\rfloor) min(si,vij)个物品 i i i的情况

状态转移方程:
f ( i , j ) = m a x ( f ( i , j ) , f ( i − 1 , j − k v i ) + k w i ) ( k ∈ [ 0 , m i n ( s i , ⌊ j v i ⌋ ) ] , k ∈ Z ) f(i,j)=max(f(i,j),f(i-1,j-kv_i)+kw_i)\quad(k\in [0,min(s_i,\lfloor\frac{j}{v_i}\rfloor)],k\in Z) f(i,j)=max(f(i,j),f(i1,jkvi)+kwi)(k[0,min(si,vij)],kZ)

时间复杂度: O ( n 3 ) O(n^3) O(n3)

for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
		for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
			f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);

优化:转化为 01 01 01背包问题

方法:二进制拆分

假设物品 i i i 1000 1000 1000件,则可以选择的数量有 0 , 1 , 2 , ⋯   , 1000 0,1,2,\cdots,1000 0,1,2,,1000,我们将物品按 1 , 2 , 4 , ⋯   , 256 , 489 ( 1,2,4,\cdots,256,489( 1,2,4,,256,489(此处为 1000 − 511 ) 1000-511) 1000511)件进行“打包”,则可将问题转化为“打包”后新 ⌈ log ⁡ n ⌉ \lceil \log n\rceil logn个物品的01背包问题,时间复杂度 O ( n 3 ) O(n^3) O(n3)变为 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)

//这里注意N至少要开到n*log(s)
int v[N],w[N];
int f[N];
int n,m,cnt;
//边读边打包
for(int i=1;i<=n;i++){
	int a,b,s;
	scanf("%d%d%d",&a,&b,&s);
	//打包存储,cnt记录打包后物品总数量 
	int k=1;
	while(k<=s){
		cnt++;
		v[cnt]=a*k;
		w[cnt]=b*k;
		s-=k;
		k*=2;
	}
	//剩余不足2^(k+1)个物品额外打包 
	if(s>0){
		cnt++;
		v[cnt]=a*s;
		w[cnt]=b*s;
	}
} 
//物品个数为cnt,背包容量为m的01背包问题
for(int i=1;i<=cnt;i++)
	for(int j=m;j>=v[i];j--)
		f[j]=max(f[j],f[j-v[i]]+w[i]);

利用单调队列优化多重背包,时间复杂度可以达到 O ( n 2 ) O(n^2) O(n2)

4.分组背包问题

分组背包问题特点:物品分为多组,每组内有若干个,每组只能选一个

状态表示:定义 f ( i , j ) f(i,j) f(i,j)表示只从 i i i物品中选择,总体积不超过 j j j的最大价值

状态转移: f ( i , j ) f(i,j) f(i,j)可以分为不从第 i i i组物品中选和从第 i i i组物品中选第 k k k个物品两种情况

状态转移方程:
f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − v ( i , k ) ) + w ( i , k ) ) f(i,j)=max(f(i-1,j),f(i-1,j-v_{(i,k)})+w_{(i,k)}) f(i,j)=max(f(i1,j),f(i1,jv(i,k))+w(i,k))

优化:从二维变成一维(类比 01 01 01背包, j j j从大到小遍历)

int n,m;//n组物品,容量为m
int v[N][N],w[N][N],s[N];//s[i]表示第i组物品的件数
int f[N];
for(int i=1;i<=n;i++)
	for(int j=m;j>=0;j--)
		for(int k=1;k<=s[i];k++)
			if(v[i][k]<=j)
				f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);

四、树形 D P DP DP

1.树的相关知识

树的直径:树上的最长链

求解方法:

( 1 ) (1) (1)两次 D F S DFS DFS

  1. 任取树上一点 P P P,进行一次 D F S DFS DFS,找到离 P P P最远的点 Q Q Q
  2. 再以点 Q Q Q为起点进行一次 D F S DFS DFS,找到离 Q Q Q最远的点 W W W,此时 Q Q Q W W W间的距离就是树的直径

( 2 ) (2) (2)树形 D P DP DP

对于每个节点我们需要记录两个值:

  1. f 1 ( i ) f1(i) f1(i)表示以 i i i为根的子树中 i i i到叶子节点的最大值
  2. f 2 ( i ) f2(i) f2(i)表示以 i i i为根的子树中 i i i到叶子节点的次大值

更新时,考虑贪心,先看是否能更新 f 1 f1 f1的值,再看是否能更新 f 2 f2 f2的值

  1. f 1 ( i ) < f 1 ( j ) + w ( i , j ) f1(i)< f1(j)+w(i,j) f1(i)<f1(j)+w(i,j),则 f 2 ( i ) = f 1 ( i ) , f 1 ( i ) = f 1 ( j ) + w ( i , j ) f2(i)= f1(i),f1(i)= f1(j)+ w(i,j) f2(i)=f1(i),f1(i)=f1(j)+w(i,j)
  2. 否则,若 f 2 ( i ) < f 1 ( j ) + w ( i , j ) f2(i)< f1(j)+w(i,j) f2(i)<f1(j)+w(i,j),则 f 2 ( i ) = f 1 ( j ) + w ( i , j ) f2(i)=f1(j)+w(i,j) f2(i)=f1(j)+w(i,j)

最终的结果: m a x { f 1 ( i ) + f 2 ( i ) } max\{f1(i)+f2(i)\} max{f1(i)+f2(i)}

树的重心:删掉某个点后剩下的最大连通块大小最小

对于删掉某个节点,剩下的连通块大小分别是:

  1. 以删掉点的儿子为根的子树大小
  2. 整棵树大小减去以删掉点为根节点的子树大小
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值