动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 -----OI Wiki
例.1-最大子段和
分析
DP四步
⑴定义状态
定义\(dp_i\)表示以\(i\)结尾的最大子段和
⑵分析答案
答案即\({\max}^{i\in[1,n]}_{dp_i}\)
⑶分析方程
对于每个\(i\):
- 可以与\([1,i-1]\)的最大子段和拼接,组成新的子段和\((dp_{i-1}+a_i)\)
- 可以自己单独成一个子段和\(a_i\)
求\(\max\)
⑷边界条件
即\(dp_1\)为\(a_1\)
代码实现
递归写法
定义\(f(i)\)为以\(i\)结尾的最大子段和
则递归分析即可
int f(int x){if(x==1){//边界条件return a[1];}return max(f(x-1)+a[x],a[x]);//求最大值
、}
这样时间很慢,原因是存在许多已经算过的节点被重复计算
所以用一个\(val\)记录计算过的节点信息
for(int i=1;i<=n;i++){dp[i]=inf;
}
int f(int x){if(dp[x]!=inf){return dp[x];//已经记录过的节点信息}if(x==1){//边界条件return a[1];}dp[x]=max(f(x-1)+a[x],a[x]);//求最大值return dp[x];
}
上述优化方法即记忆化搜索,是一种基本DP方法
记忆化搜索是一种通过记录已经遍历过的状态的信息,从而避免对同一状态重复遍历的搜索实现方式。 ------OI Wiki
转成递推形式就成了基本的线性DP
dp[1]=a[1];
for(int i=2;i<=n;i++){dp[i]=max(dp[i-1]+a[i],a[i]);maxi=max(maxi,dp[i]);
}
例.2-最长上升子序列(LIS)
分析
DP四步
⑴定义状态
定义\(dp_i\)表示以\(i\)结尾的最长上升子序列长度
⑵分析答案
答案即\({\max}^{i\in[1,n]}_{dp_i}\)
⑶分析方程
对于每个\(i\),有若干\(j<i\)且\(a_j<a_i\):
- 可以与每一个\(j\)的最长上升子序列拼接,组成新的子序列长度\((dp_{j}+1)\)
- 可以自己单独成一个子段和\(1\)
求\(\max\)
⑷边界条件
即\(dp_i\)为\(1\),因为每个\(dp\)值至少为\(1\)
代码实现
使用递推,枚举\(i\),并且枚举\(j(j<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[j]+1,dp[i]);}}maxi=max(maxi,dp[i]);
}
重点题-导弹拦截
50分做法
第一小问即求最长不上升子序列长度
第二问可以用Dilworth 定理解决
把序列分成不上升子序列的最少个数,等于序列的最长上升子序列长度。把序列分成不降子序列的最少个数,等于序列的最长下降子序列长度。
所以第二问等价于求最长上升子序列长度
100分做法
使用贪心优化如果,如果一个位置可以有\(2,3\)两个数选一个数,我们一定会选\(2\),因为选2后面就有更多的机会拼接。
定义一个\(c\)数组存贮已经选了的数
只要每次二分查找第一个能够等价替换的数,就能将其替换,在过程中记录DP即可。
具体代码