• 区间dp(模板+例题)


    参考博文:区间dp小结(附经典例题)
    首先,什么是区间dp?它是干什么的?

    1. 先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解
    2. 操作往往涉及到区间合并问题

    以上。


    模板如下:

     1 //mst(dp,0) 初始化DP数组  
     2 for(int i=1;i<=n;i++)  
     3 {  
     4     dp[i][i]=初始值  
     5 }  
     6 for(int len=2;len<=n;len++)  //区间长度  
     7 for(int i=1;i<=n;i++)        //枚举起点  
     8 {  
     9     int j=i+len-1;           //区间终点  
    10     if(j>n) break;           //越界结束  
    11     for(int k=i;k<j;k++)     //枚举分割点,构造状态转移方程  
    12     {  
    13         dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);  
    14     }  
    15 }  

    注意区间的枚举起点。


    例题1:51Nod1021 石子合并
    题意:
    N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。

    首先,假如合并的次序没有限制,那么我们将每堆石子看成一个叶结点,每次合并的代价为数中结点,采用贪心的策略,则整个合并过程就是一个哈夫曼树的建树过程。

    但是这里合并的时候要求每次只能合并相邻的两堆,则贪心这里就会出错了,因此我们采用dp的思想进行求解。

    我们用dp[i][j]来表示合并第i堆到第j堆石子的最小代价,那么状态转移方程为:
    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);

    其中w[i][j]表示把两部分合并起来的代价,即从第i堆到第j堆石子个数的和,为了方便查询,我们可以用sum[i]表示从第1堆到第i堆的石子个数和,那么w[i][j]=sum[j]-sum[i-1].

    代码如下:

     1 #include <cstdio>  
     2 #include <queue>  
     3 #include <cstring>  
     4 #include <algorithm>  
     5 using namespace std;  
     6 #define mst(a,b) memset((a),(b),sizeof(a))  
     7 #define rush() int T;scanf("%d",&T);while(T--)  
     8 
     9 typedef long long ll;  
    10 const int maxn = 205;  
    11 const ll mod = 1e9+7;  
    12 const ll INF = 1e18;  
    13 const double eps = 1e-9;  
    14 
    15 int n,x;  
    16 int sum[maxn];  
    17 int dp[maxn][maxn];  
    18 
    19 int main()  
    20 {  
    21     while(~scanf("%d",&n))  
    22     {  
    23         sum[0]=0;  
    24         mst(dp,0x3f);  
    25         for(int i=1;i<=n;i++)  
    26         {  
    27             scanf("%d",&x);  
    28             sum[i]=sum[i-1]+x;  
    29             dp[i][i]=0;  
    30         }  
    31         for(int len=2;len<=n;len++)  
    32         for(int i=1;i<=n;i++)  
    33         {  
    34             int j=i+len-1;  
    35             if(j>n) continue;  
    36             for(int k=i;k<j;k++)  
    37             {  
    38                 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);  
    39             }  
    40         }  
    41         printf("%d
    ",dp[1][n]);  
    42     }  
    43     return 0;  
    44 }  

    当然有的时候直接这样裸着O(n^3)会T,所以我们有平行四边形优化版本:

    由于状态转移时是三重循环的,我们想能否把其中一层优化呢?尤其是枚举分割点的那个,显然我们用了大量的时间去寻找这个最优分割点,所以我们考虑把这个点找到后保存下来

    用s[i][j]表示区间[i,j]中的最优分割点,那么第三重循环可以从[i,j-1)优化到【s[i][j-1],s[i+1][j]】。(这个时候小区间s[i][j-1]和s[i+1][j]的值已经求出来了,然后通过这个循环又可以得到s[i][j]的值)。

    代码如下:

     1 #include <cstdio>  
     2 #include <queue>  
     3 #include <cstring>  
     4 #include <algorithm>  
     5 using namespace std;  
     6 #define mst(a,b) memset((a),(b),sizeof(a))  
     7 #define rush() int T;scanf("%d",&T);while(T--)  
     8 
     9 typedef long long ll;  
    10 const int maxn = 205;  
    11 const ll mod = 1e9+7;  
    12 const ll INF = 1e18;  
    13 const double eps = 1e-9;  
    14 
    15 int n,x;  
    16 int sum[maxn];  
    17 int dp[maxn][maxn];  
    18 int s[maxn][maxn];  
    19 
    20 int main()  
    21 {  
    22     while(~scanf("%d",&n))  
    23     {  
    24         sum[0]=0;  
    25         mst(dp,0x3f);  
    26         for(int i=1;i<=n;i++)  
    27         {  
    28             scanf("%d",&x);  
    29             sum[i]=sum[i-1]+x;  
    30             dp[i][i]=0;  
    31             s[i][i]=i;  
    32         }  
    33         for(int len=2;len<=n;len++)  
    34         for(int i=1;i<=n;i++)  
    35         {  
    36             int j=i+len-1;  
    37             if(j>n) continue;  
    38             for(int k=s[i][j-1];k<=s[i+1][j];k++)  
    39             {  
    40                 if(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]<dp[i][j])  
    41                 {  
    42                     dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];  
    43                     s[i][j]=k;  
    44                 }  
    45             }  
    46         }  
    47         printf("%d
    ",dp[1][n]);  
    48     }  
    49     return 0;  
    50 }  

    例题2:hdu3506 猴子派对
    题意:
    问题转化后其实就是环形石子合并,即现在有围成一圈的若干堆石子,其他条件跟其那面那题相同,问合并所需最小代价。
    解法:
    把前n-1堆石子一个个移到第n个后面,那样环就变成了线,即现在有2*n-1堆石子需要合并,我们只要求下面的式子即可。求法与上面那题完全一样。

    这道题在uestc版本上不做平行四边形优化会T。

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<string.h>
     4 using namespace std;
     5 typedef long long ll;
     6 const int inf = 99999999;
     7 const int maxn = 10000;
     8 int a[maxn];
     9 int sum[maxn], dp[maxn][maxn], s[maxn][maxn];
    10 
    11 int main() {
    12     int n;
    13     while (scanf("%d", &n) != EOF) {
    14         for (int i = 1; i <= n; i++) {
    15             scanf("%d", &a[i]);
    16             a[n + i] = a[i];
    17         }
    18         for (int i = 1; i <= 2 * n; i++) 
    19             sum[i] = sum[i - 1] + a[i];
    20         for (int m = 1; m <= n; ++m) {
    21             for (int i = 0; i + m - 1 < 2 * n; ++i) {
    22                 int j = i + m - 1;
    23                 if (m == 1) {
    24                     dp[i][i] = 0;
    25                     s[i][i] = i;
    26                 }
    27                 else {
    28                     dp[i][j] = inf;
    29                     for (int k = s[i][j - 1]; k <= s[i + 1][j]; ++k) {
    30                         if (dp[i][j] > dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]) {
    31                             dp[i][j] = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1];
    32                             s[i][j] = k;
    33                         }
    34                     }
    35                 }
    36             }
    37         }
    38         int ans = inf;
    39         for (int i = 1; i <= n; ++i) {
    40             ans = min(ans, dp[i][n + i - 1]);
    41         }
    42         printf("%d
    ", ans);
    43     }
    44     return 0;
    45 }

    例题3:hdu5115 Dire wolf
    题意:
    有一排狼,每只狼有一个伤害A,还有一个伤害B。杀死一只狼的时候,会受到这只狼的伤害A和这只狼两边的狼的伤害B的和。如果某位置的狼被杀,那么杀它左边的狼时就会收到来自右边狼的B,因为这两只狼是相邻的了。求杀掉一排狼的最小代价。

    解法:
    设dp[i][j]为消灭编号从i到j只狼的代价,那么结果就是dp[1][n],枚举k作为最后一只被杀死的狼,此时会受到a[k]和b[i-1] b[j+1]的伤害,取最小的即可

    可列出转移方程:
    dp[i][j]=min(dp[i][j], dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
    dp[i][i]=a[i]+b[i-1]+b[i+1];

    代码如下:

     1 #include<stdio.h>
     2 #include<iostream>
     3 #include<string.h>
     4 #include<algorithm>
     5 #include<math.h>
     6 using namespace std;
     7 const int N=220
     8 const int INF=0xfffffff
     9 
    10 int main()
    11 {
    12     int T, n, a[N], b[N], dp[N][N], t=1;
    13     scanf("%d", &T);
    14     while(T--)
    15     {
    16         memset(a, 0, sizeof(a));
    17         memset(b, 0, sizeof(b));
    18         scanf("%d", &n);
    19         for(int i=1; i<=n; i++)
    20             scanf("%d", &a[i]);
    21         for(int i=1; i<=n; i++)
    22             scanf("%d", &b[i]);
    23         for(int i=1; i<=n; i++)
    24         {
    25             for(int j=i; j<=n; j++)
    26                 dp[i][j]=INF;
    27      ///       dp[i][i]=a[i]+b[i-1]+b[i+1];
    28         }
    29         for(int l=0; l<=n; l++)//注意边界
    30         {
    31             for(int i=1; i+l<=n; i++)
    32             {
    33                 int j=i+l;
    34                 for(int k=i; k<=j; k++)
    35                 {
    36                     dp[i][j]=min(dp[i][j], dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
    37                 }
    38             }
    39         }
    40         printf("Case #%d: %d
    ", t++, dp[1][n]);
    41     }
    42     return 0;
    43 }
  • 相关阅读:
    织梦dedecms模板中调用wordpress文章
    dede标签云(TAG)随机颜色及大小的实现方法
    将dedecms数据转换到wordpress博客程序中的方法分享
    织梦dedecms模板中友情链接标签底层模板样式调整
    织梦dede增加自定义属性四步实现
    dedecms专题分节点自由单独调用的实现方法
    DedeCMS 批量取消审核文档的实现方法
    织梦dedecms 5.1 utf-8版本英文修改方法
    织梦dedecms后台自定义字段里添加style全部都变成st<x>yle的解决教程
    Android 获取第三方软件的包名、入口Activity的类名
  • 原文地址:https://www.cnblogs.com/romaLzhih/p/9489811.html
Copyright © 2020-2023  润新知