zoukankan      html  css  js  c++  java
  • 动态规划归纳(基础篇)

    概要

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

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

    求最大连续子序列和

    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我更相信我的暴力!!!暴力出奇迹

  • 相关阅读:
    Silverlight开发历程—(用C#来绘制图形)
    Silverlight之dispatcherTimer 与 线程
    Silverlight开发历程—RenderTransform特效(TranslateTransform,RotateTransform,ScaleTransform,skewTransform)
    百度程序员猝死 是否过劳死引发争论
    10款优秀的HTML5开发工具
    Silverlight开发历程—(绘制报表)
    IT的哥一样是传说!
    Silverlight开发历程—(布局控件Canvas)
    Silverlight开发历程—(利用C#代码制作取色器)
    Silverlight开发历程—(画刷与着色之线性渐变画刷)
  • 原文地址:https://www.cnblogs.com/SJum/p/9489882.html
Copyright © 2011-2022 走看看