动态规划归纳(基础篇)

概要

  对于对动态规划不是特别精通的我,写的一篇大佬看了掉头离开的杂文。

  主要归纳一些常见的、基础的动态规划的模型。

 

求最大连续子序列和

Description

有一个整数数列,求一个连续子序列,使得子序列的和最大。

Input

第一行,n {表示该数列有n个整数,n <= 10000 } 
第二行,n个整数(integer类型),每个整数之间有一个空格。

Output

一行,一个值,最大连续子序列和(结果保证在正负 2^31 之间)。

Sample Input

6
3 2 -20 12 15 -100

Sample Output

27

代码及注释

 1 #include<iostream>
 2 #include<cstdio>
 3 #define MAXN 10005
 4 #define INF 0x3f3f3f3f
 5 using namespace std;
 6 
 7 int n;
 8 int a[MAXN], maxsum[MAXN];
 9 int ans = -INF;
10 
11 int main() {
12     
13     scanf("%d", &n);
14     for (int i = 1; i <= n; i++)
15         scanf("%d", &a[i]);
16     
17     for (int i = 1; i <= n; i++) {
18         maxsum[i] = maxsum[i - 1] + a[i];    //求子序列的和
19         ans = max(maxsum[i], ans);        //更新答案
20         if (maxsum[i] < 0) maxsum[i] = 0;    //若该子序列对于答案的最大化没有贡献了,舍弃之
21     }
22     
23     printf("%d", ans);
24     
25     return 0;
26 }

 

 

求最长不下降序列

Description

  设有一个正整数的序列:b1,b2,…,bn,若对于下标 i1 < i2 < i3 < … < ik(注:下标 i1 < i2 < i3 < … < ik,不一定是连续的!!),有 bi1≤bi2≤…≤bik,则称存在一个长度为K的不下降序列。如:数列23,17,19,26,48 对于下标 i1=1, i2=4, i3=5, 且满足23<26<48,则存在长度为3的不下降序列。问题为:当给定一列数时,求出其最长的不下降序列。 

Input

  第一行是数据的个数N,第二行是N个整数( N <= 1000 )。

Output

  一行,最长不下降序列的长度(即最长不下降序列的数据个数)。

Sample Input

5
7 13 9 16 28

Sample Output

 

4

 

{样例的结果为:7 9 16 28,此部分无须输出}

代码及注释

 1 #include<iostream>
 2 #include<cstdio>
 3 #define MAXN 1005
 4 #define INF 0x3f3f3f3f
 5 using namespace std;
 6 
 7 int n, a[MAXN];
 8 int maxlen[MAXN];
 9 int ans = -INF;
10 
11 int main() {
12     
13     scanf("%d", &n);
14     for (int i = 1; i <= n; i++) {
15         scanf("%d", &a[i]);
16         maxlen[i] = 1;        //初始化单个元素一个长度为1的子序列
17     }
18     
19     for (int i = 1; i <= n; i++)    //阶段:到第i个元素时最优解
20         for (int j = 1; j < i; j++)    //寻找最优继承状态
21             if (a[j] <= a[i] && maxlen[j] + 1 > maxlen[i]) {  //满足最长不下降且可优化阶段解
22                 maxlen[i] = maxlen[j] + 1;
23                 ans = max(ans, maxlen[i]);
24             }
25             
26     printf("%d", ans);
27         
28     return 0;
29 }


最长公共子序列

Description

  输入2个字符串A和B,要求找出A和B共同的最长子序列,可以不连续,但顺序不能起颠倒。例如:A=‘abdcef’ , B=‘jakfdaca’, 此时存在下列子序列:‘adc’,长度为3,在A和B中都存在,且顺序相同,所以是符合要求的子序列。 
[要求]从文件substr.in中读入两个字符串A和B(文件中有二行,第一行为字符串A,第二行为字符串B,字符串的最大长度不超过200),找出A和B中最长公共子序列,并输出最长公共子序列的长度,结果输出到substr.out。 

Input

  文件中有二行,第一行为字符串A,第二行为字符串B,字符串的最大长度不超过200。

Output

  输出最长公共子序列的长度。

Sample Input

abdcef
jakfdaca

Sample Output

3

思想

  设 f [ i ] [ j ] 为到 str1 的 i 位,到 str2 的 j 位的最长公共子序列。

  1.当 str1[ i ] != str2[ j ] 时, f [ i ] [ j ] = max { f [ i - 1 ] [ j ] , f[ i ] [ j - 1 ] } ,即在前一位找相同字母

  2.当 str1[ i ] == str2[ j ]时,f [ i ] [ j ] = f [ i - 1 ] [ j - 1 ] + 1 ,可增加子序列长度

代码

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 
 5 char str1[205], str2[205];
 6 int f[205][205];
 7 
 8 int main() {
 9     
10     scanf("%s%s", &str1, &str2);
11     for (int i = 0; i < strlen(str1); i++)
12         for (int j = 0; j < strlen(str2); j++) {
13             if (str1[i] != str2[j]) f[i][j] = max(f[i - 1][j], f[i][j - 1]);
14             else f[i][j] = f[i - 1][j - 1] + 1;
15         }
16     printf("%d", f[strlen(str1) - 1][strlen(str2) - 1]);
17     
18     return 0;
19 }

 

背包问题模型

(不贴代码了…)

01背包 : 采药

https://www.luogu.org/problemnew/show/P1048

状态转移方程:F[ v ] = max { F[ v ] , F[ v - c [ i ] ] + w[ i ] }

完全背包:疯狂的采药

https://www.luogu.org/problemnew/show/P1616

完全背包的方程与01背包的方程相同,在动态规划的时候枚举背包容量时逆推即可

有关背包的计数问题:小A点菜

https://www.luogu.org/problemnew/show/P1164

状态转移方程:F[ j ] = F[ j ] + F[ j - a[ i ] ]

动归顺序按照完全背包,注意逆推

 

区间DP模型

经典问题:合并沙子

 

Description

 

  设有N沙子排成一排,其编号为1,2,……,N(N小于500),每堆子有一定的数量,用a[k]表示第K堆沙子的数量值,现在要将N堆沙子归并成为一堆,归并的过程为每次只能将相邻的两堆沙子堆成一堆,合并后的这堆沙子的代价为这两堆沙子的数值和,这样经过N-1次归并之后,最后成为一堆。不同的归并方案的总代价值是不同的。现给出N堆沙子的数量后,找出一种合理的归并方法,使总的归并代价为最小。 

 

Input

 

  有 n+1 行,第一行为n的值,从第2行开始,为n个正整数,表示各堆沙子的数量值。 

 

Output

 

  只有一行,表示最小的总代价,结果保证小于2^31。

 

Sample Input

 

10                          { n 值 }
12                          { 以下,每行一个数值,共n行 }
3
13
7
8
23
14
6
9
34

 

Sample Output

 

398

代码及注释

 1 #include<iostream>
 2 #include<cstdio>
 3 #define INF 0x7fffffff
 4 using namespace std;
 5 
 6 int n, a[505], sum[505], f[505][505];
 7 
 8 int main() {
 9     
10     scanf("%d", &n);
11     for (int i = 1; i <= n; i++) {
12         scanf("%d", &a[i]);
13         sum[i] = sum[i - 1] + a[i];        //计算前缀和
14     }
15     for (int k = 2; k <= n; k++)            //阶段:区间长度
16         for (int i = 1; i <= n - k + 1; i++) {    //区间起点为位置i
17             int j = i + k - 1;        //定义区间终点
18             if (j > n) break;        //区间超出范围则返回
19             f[i][j] = INF;            //求最小值,赋初始值为+INF
20             for (int l = i; l <= j; l++)    //枚举中断点
21                 f[i][j] = min(f[i][j], f[i][l] + f[l + 1][j] + sum[j] - sum[i - 1]);    //DP
22         }
23     printf("%d", f[1][n]);
24     
25     return 0;
26 }

 

归纳

  区间DP的方式比较固定,首先阶段为区间的长度,把一个大区间化为长度很小的子区间,问题分解,之后枚举区间位置和中断点位置。因此可以得出一个区间DP模板

for (int k = 2; k <= /*区间长度*/; k++)
    for (int i = 1; i <= /*区间长度*/; i++) { //子区间起点
        int j = i + k - 1; //子区间终点
        /*f[i][j] = INF 若求最小值 */
        for (int l = i; l <= j; l++)    //枚举中转点
            f[i][j] = max /* or min */ (f[i][j], f[i][l] + f[l + 1][j] + val[i][j] /* i到j的value */;
    }

 

进阶:环形区间DP

https://www.luogu.org/problemnew/show/P1063

拿能量项链这一道题来示例。

环形DP的方法主要有两种,第一种:对于每一个枚举元素下标的变量取%,但是比较难实现。所以第二种:延伸。将原区间伸长一倍进行DP,最终答案在1到2*n中的1到n的区间里(具体看下列代码解释)

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 
 5 int a[500], n, x;
 6 long long f[500][500];
 7 long long ans = 0;
 8 
 9 int main() {
10     
11     scanf("%d", &n);
12     for (int i = 1; i <= n; i++) {
13         scanf("%d", &a[i]);
14         a[i + n] = a[i];        //延伸一倍
15     }
16     for (int i = 1; i <= 2 * n; i++)
17         f[i - 1][i] = a[i - 1] * a[i] * a[i + 1]; //预处理:将相邻两个能量项链合并所生成的能量
18     
19     for (int k = 2; k <= n; k++)
20         for (int i = 1; i <= 2 * n; i++) {
21             int j = i + k - 1;
22             for (int l = i; l < j; l++)
23                 f[i][j] = max(f[i][j], f[i][l] + f[l + 1][j] + a[i] * a[l + 1] * a[j + 1]);
24         }
25     for (int i = 1; i <= n; i++)
26         if (f[i][i + n - 1] > ans) ans = f[i][i + n - 1];    //答案不在f[1][n]中,在1-2*n的1-n中
27     printf("%lld", ans);
28     
29     return 0;
30 }

 

尾声

主要都是一些很常见基础的模型,至于其他的一些奇奇怪怪的DP,ummm我更相信我的暴力!!!暴力出奇迹

posted @ 2018-08-16 20:57  StupidJum  阅读(353)  评论(0编辑  收藏  举报