前言
-
什么是动态规划?
- 动态规划是求解决策过程最优化的数学方法。把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解。
-
什么时候要用动态规划?
- 如果要求一个问题的最优解(通常是最大值或者最小值),而且该问题能够分解成若干个子问题,并且小问题之间也存在重叠的子问题,则考虑采用动态规划。
-
动态规划的前提
- 最优化原理(最优子结构性质)
- 最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。
- 无后效性
- 将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策
- 子问题的重叠性
- 最优化原理(最优子结构性质)
-
解题步骤
1.确定题意要求的最优解是什么
2.确定已知量有哪些
3.根据已知量确定dp的状态有哪些维
4.从下而上的分析问题,找到关联
5.确定状态转移方程
6.确定底层边界的答案
如果出现方程无法确定的情况,比如子结构无法最优,有后效性的时候先考虑换状态
例题
一、字符串相关
- 小概念
-
对于一个字符串而言,比如:pikachu
-
子串(substring):在字符串中,取出一块(连续的),如:pik, ach, kac等
-
子序列(subsequence):从字符串中,顺序取出字符,但是可以不连续:如:pau, kch, icu等
-
一个长度为n的字符串有[n*(n+1)]/2子串,有2^n-1子序列?(不考虑空串)
-
- 最长公共子序列
-
给两个字符串,求他们的最长公共子序列长度
-
例如abcd和befgc的最长公共子序列的长度是2(bc)
-
状态:设数组dp[m][n]来表示长度为m的str1和长度为n的str2的最长公共子序列长度
-
if (m[i] == n[j]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
-
int dp[10000][10000];
int main() {
string m, n;
cin >> m >> n;
int cntm = m.size();
int cntn = n.size();
m = "\0" + m;
n = "\0" + n;
for (int i = 1; i <= cntm; i++)
for (int j = 1; j <= cntn; j++) {
if (m[i] == n[j]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
p(dp[cntm][cntn]);
}
/* 建模
* b e f g c
* a 0 0 0 0 0
* b 1 1 1 1 1
* c 1 1 1 1 2
* d 1 1 1 1 2
*/
- 最长递增子序列
-
给定一个数列,求他的最长递增子序列长度
-
例如:1 2 3 1 2 10的最长递增子序列长度是4(1 2 3 10)
-
状态:dp[n]代表1-n的最长递增子序列长度
-
方程:
dp[n]=1;
if(a[n]>a[m]) dp[n]=max(dp[n],dp[m]+1); m<n
-
const int maxn = 1e6 + 7;
int n, a[maxn], dp[maxn];//a[]储存输入的值,dp[n]为以a[n]结尾的最长递增子序列长度
int main() {
sc(n);
for (int i = 1; i <= n; i++) sc(a[i]);
for (int i = 1; i <= n; i++) {
dp[i] = 1;
for (int j = 1; j < i; j++) {
if (a[i] > a[j])
dp[i] = max(dp[i], dp[j] + 1);//线段树优化,dp[i]=max{dp[j]}+1;
}
}
for (int i = 1; i <= n; i++) cout << a[i] << " ";
cout << endl;
for (int i = 1; i <= n; i++) cout << dp[i] << " ";
}