清北学堂noip2019集训D5——动态规划

动态规划

动态规划难点

  • 意识到这个题用动态规划解决
  • 高效设计状态
  • 多做题

最长上升子序列问题

  • 题目: ()
  • 设计状态:
  • f [ i ] f[i] f[i]表示以 a [ i ] a[i] a[i]结尾的最长上升子序列长度。
  • 最终答案 m a x 1 ≤ i ≤ n max_1\leq i \leq n max1in
  • 如何转移?
for(int i=1;i<=n;i++){
	f[i]=1;
	for(int j=1;j<i;j++){
		if(a[i]<a[j]) f[i]=max(f[i],1+f[j]);
	}
}
  • 时间复杂度 O ( n 2 ) O(n^2) O(n2)
  • 如何优化?
方法一:树状数组优化
  • 假设 a i a_i ai互不相同。
  • 离散化: a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an是一个 { 1 , 2 , . . . , n } \{1,2,...,n\} {1,2,...,n}的排列
  • DP值 f [ i ] f[i] f[i]插入数组的 a i a_i ai位置(这里树状数组用于求前缀max)
  • 离散化?
//a[i]互不相同时
int a[N];
int a2[N];
int ord[N];
int cmp(int i,int j){
	return a[i]<a[j];
}
void lsh(){
	for(int i=0;i<=n;i++){
		ord[i]=i;
	}
	sort(ord+1,ord+n+1,cmp);
	for(int i=1;i<=n;i++){
		a2[ord[i]]=a[i];
	}
}
  • 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
方法二:“单调栈”
  • g [ j ] g[j] g[j]表示目前为止,长度为j的上升子序列的末尾元素最小是多少。
  • a [ 1...5 ] = { 1 , 5 , 2 , 4 , 3 } a[1...5]=\{1,5,2,4,3\} a[1...5]={1,5,2,4,3}
    g [ 1 ] = i n f g[1]=inf g[1]=inf
    插入1, g [ 1 ] = 1 , g [ 2 ] = i n f g[1]=1,g[2]=inf g[1]=1,g[2]=inf
    插入5, g [ 1 ] = 1 , g [ 2 ] = 5 , g [ 3 ] = i n f g[1]=1,g[2]=5,g[3]=inf g[1]=1,g[2]=5,g[3]=inf
    插入2, g [ 1 ] = 1 , g [ 2 ] = 2 , g [ 3 ] = i n f g[1]=1,g[2]=2,g[3]=inf g[1]=1,g[2]=2,g[3]=inf
    插入4, g [ 1 ] = 1 , g [ 2 ] = 2 , g [ 3 ] = 4 , g [ 4 ] = i n f g[1]=1,g[2]=2,g[3]=4,g[4]=inf g[1]=1,g[2]=2,g[3]=4,g[4]=inf
    插入3, g [ 1 ] = 1 , g [ 2 ] = 2 , g [ 3 ] = 3 , g [ 4 ] = i n f g[1]=1,g[2]=2,g[3]=3,g[4]=inf g[1]=1,g[2]=2,g[3]=3,g[4]=inf
  • 如何快速更新?
  • 二分查找?
  • 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
  • 假如输入数列有重复?
    映射到不同的值上。
例题
  • 输入一个整数序列 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,最少需要修改几次使整个序列变成严格上升序列?(要求修改后均为整数)。
  • a i a_i ai减去 i i i,得到一个数列,求n-该数列最长不下降子序列长度。

最长公共子序列

  • 状态: f [ i ] [ j ] f[i][j] f[i][j]表示 s [ 1.. i ] , t [ 1 , , j ] s[1..i],t[1,,j] s[1..i],t[1,,j]的最长公共子序列
  • 转移方程:
    s [ i ] = t [ j ] : f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + 1 s[i]=t[j]:f[i][j]=f[i-1][j-1]+1 s[i]=t[j]f[i][j]=f[i1][j1]+1
    s [ i ] ≠ t [ j ] : f [ i ] [ j ] = m a x { f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] } s[i]\neq t[j]:f[i][j]=max\{f[i-1][j],f[i][j-1]\} s[i]̸=t[j]f[i][j]=max{f[i1][j],f[i][j1]}
  • 时间复杂度 O ( n 2 ) O(n^2) O(n2)
如果 s , t ≤ 100000 s,t\leq100000 s,t100000呢?
  • 保证s中每一个元素出现的次数小于10次。
思路
  • 如果互不相同
  • 让t中在s中没有出现过的元素删去,让t以s的顺序排序,算一个最长上升子序列。
  • 那次数 ≤ 10 \leq10 10呢?
  • 转化为LIS(最长上升子序列)
  • 例如:
    s=abdbad
    t=abcbd
    a:5,1
    b:4,2
    c:。。
    d:6,3
    所以t转化为5,1,4,2,4,2,6,3
    再求t的最长上升子序列。
  • 总结起来就是:
    将每个元素在s中出现的下表以降序存在一个链表里,未完。。。

DAG上的dp

  • 输入一个有向无环图(DAG),并给定起点s,边权为正,计算从s到每个点u的最长路长度 d [ u ] d[u] d[u].
  • 要求时间复杂度 O ( n + m ) O(n+m) O(n+m)
  • 先进行拓扑排序 v 1 , v 2 . . . , v n v_1,v_2...,v_n v1,v2...,vn
    假设 v 1 = s v_1=s v1=s。(否则,s走不到排在s前面的点,可以直接设置它们最短路为-inf)
  • d [ v i ] = 0 d[v_i]=0 d[vi]=0
    i = 2 , . . . , n i=2,...,n i=2,...,n,枚举所有指向 v i v_i vi的边,计算的 d [ v i ] = m a x { d [ v j ] + w ( v j , v i ) } d[v_i]=max\{d[v_j]+w(v_j,v_i)\} d[vi]=max{d[vj]+w(vj,vi)}
记忆化搜索

记忆化搜索

背包问题

01背包
  • n n n个物品,每个有体积 w [ i ] w[i] w[i],价值 v [ i ] v[i] v[i]。总体积为 W W W的背包最多能装多少总价值?
  • ( n , W ≤ 2000 ) (n,W\leq2000) (n,W2000)
  • 设计状态: f [ i ] [ j ] f[i][j] f[i][j]表示 j j j体积装前 i i i件物品,最大的总价值。
  • 如何转移?
    f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]) f[i][j]=max(f[i1][j],f[i1][jw[i]]+v[i])
    for(int i=1;i<=n;i++){
    	for(int j=0;j<=w[i];j++)
    		f[i][j]=f[i-1][j];
    	for(int j=w[i];j<=W;j++){
    		f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i])
    	}
    }
    

优化空间后:

	for(int i=1;i<=n;i++){
		for(int j=W;j>=w[i];j--){
			f[j]=max(f[j],f[j-w[i]]+v[i])
		}
	}
完全背包
  • n n n种物品,每种有体积 w [ i ] w[i] w[i],价值 v [ i ] v[i] v[i]且有无限个。总体积为 W W W的背包最多能装多少总价值?
  • ( n , W ≤ 2000 ) (n,W\leq2000) (n,W2000)
    for(int i=1;i<=n;i++){
    	for(int j=w[i];j<=W;j++){
    		f[j]=max(f[j],f[j-w[i]]+v[i])
    	}
    }
    
子集和问题
  • n n n个物品,第 i i i个体积为 w i w_i wi。能否选出一个子集和,使总容量为 W W W的背包装满?
  • f [ i ] [ j ] = 1 o r 0 f[i][j]=1 or 0 f[i][j]=1or0表示用前 i i i件物品能否凑出 j j j体积。
  • f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1
    for(int i=1;i<=n;i++){
    	for(int j=0;j<=W;j++)
    		f[i][j]=f[i-1][j];
    	for(int j=w[i];j<=W;j++){
    		if(f[i-1][j-w[i]])
    			f[i][j]=1;
    	}
    }
    //f[n][W]==1 or 0?
    
动态子集和问题

动态子集和问题

01背包
  • n n n个物品,每个有体积 w [ i ] w[i] w[i],价值 v [ i ] v[i] v[i]。总体积为 W W W的背包最多能装多少总价值?
  • ( n ≤ 2000 , v [ 1 ] + v [ 2 ] + ⋅ ⋅ ⋅ + v [ n ] ≤ 2000 , W ≤ 1 0 9 ) (n\leq2000,v[1]+v[2]+···+v[n]\leq2000,W\leq10^9) (n2000v[1]+v[2]++v[n]2000,W109)
  • 小技巧:反转状态和dp值
    状态 f [ i ] [ v ] f[i][v] f[i][v]表示前 i i i件物品凑出 v v v的总价值最小总体积。
    边界 f [ 0 ] [ 0 ] = 0 f[0][0]=0 f[0][0]=0
  • 伪代码:
f[0][0]=0;
for(int i=0;i<=n;i++){
	f[0][i]=inf;
}

for(int i=1;i<=n;i++){
	for(int j=0;j<=2000;j++){
		f[i][j]=f[i-1][j];
	}
	for(int j=0;j<=2000;j++){
		f[i][j]=min(f[i][j],f[i-1][j-v[i]]+w[i]);
	}
}

区间DP

合法子序列问题
  • 一个由 [ ] ( ) []() []()组成的序列,求出最长的合法子序列长度, ( n ≤ 300 ) (n\leq300) (n300)
  • 定义:合法子序列是一个合法的括号序列,每一对匹配的括号都是同一类型的。比如 [ ( ) [ ] ] ( ( ) ( ) ) [()[]](()()) [()[]](()())是合法的。
  • 设计状态:
    f [ i ] [ j ] f[i][j] f[i][j]表示在 [ i , j ] [i,j] [i,j]有多长的合法子序列。
  • 转移:
    如果 a [ l ] , a [ r ] a[l],a[r] a[l],a[r]是匹配的括号,则 2 + f [ l + 1 ] [ r − 1 ] 2+f[l+1][r-1] 2+f[l+1][r1]
    枚举断点 k : f [ l ] [ k ] + f [ k + 1 ] [ r ] k:f[l][k]+f[k+1][r] k:f[l][k]+f[k+1][r]
  • 按什么顺序进行DP?
    区间长度从小到大的顺序。
    (或者:记忆化搜索)
  • 时间复杂度: O ( n 3 ) O(n^3) O(n3)
矩阵链乘法
  • 将一个 p × q p \times q p×q的矩阵乘一个 q × r q \times r q×r的矩阵,得到一个 p × r p \times r p×r的矩阵,运算花费是 p × q × r p \times q \times r p×q×r
  • 现在输入 p 1 , p 2 , p 3 , . . . , p n p_1,p_2,p_3,...,p_n p1,p2,p3,...,pn。将 p 1 × p 2 , p 2 × p 3 , . . . , p n − 1 × p n p_1 \times p_2,p_2 \times p_3,...,p_{n-1} \times p_n p1×p2,p2×p3,...,pn1×pn的矩阵乘起来,最小的总运算花费是多少?
例题n(记不清了)

例题

  • 设计状态:
    f [ l ] [ r ] f[l][r] f[l][r]表示 a [ l , r ] a[l,r] a[l,r]字串的答案。
  • 转移: f [ l ] [ k ] + f [ k + 1 ] [ r ] f[l][k]+f[k+1][r] f[l][k]+f[k+1][r]
    当a[l]==a[r]时,未完。。。
Floyd也是dp
  • n个点的无向图,有向点和边权。每一对s,t,计算从s到t的路径,使得最小化:路径上的总边权+路径最大点权
  • ( n ≤ 300 ) (n\leq300) (n300)

状态压缩DP

经典问题
  • 求n个点的无向图的色数。(可以把顶点染成k种颜色之一,使得每条边两端不同色。最小的k是多少)
  • n ≤ 16 n\leq16 n16
  • 设计状态,用f[s]表示结点子集S的色数。枚举子集进行转移。
  • f [ S ] = m i n ( f [ S ] , 1 + f [ S − T ] ) f[S]=min(f[S],1+f[S-T]) f[S]=min(f[S],1+f[ST]),T是S的子集 ( ( T &amp; S ) = = t ) ((T\&amp;S)==t) ((T&S)==t),T中的点两两不相邻。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值