• 区间DP


    啊~~

    我来了,

    蒟蒻!!!

    十分无厘头!

    今天我们来介绍一下线性dp的进阶区间dp

    对于区间dp来说,是一类题型,也是dp的重要考点、

    对于动态规划,我们知道“阶段”是最重要的,那区间dp的状态就是区间长度。

    它常常来解决一些区间问题,由于一些区间太大,所以我们将大区间化为小区间,然后将小区间进行动态规划,最后再一步步合成所求区间。

    这就是区间dp的基本思路。当然我们一会儿将会提到记忆化搜索与区间dp的结合应用。

    首先,我们来看一道基础区间dp

    石子合并(线性版):

    题目描述

    设有N堆沙子排成一排,其编号为1,2,3,…,N(N<=300)。每堆沙子有一定的数量,可以用一个整数来描述,现在要将这N堆沙子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆沙子的数量之和,合并后与这两堆沙子相邻的沙子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同,如有4堆沙子分别为 1  3  5  2 我们可以先合并1、2堆,代价为4,得到4 5 2 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24,如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22;问题是:找出一种合理的方法,使总的代价最小。输出最小代价。

    输入格式

    第一行一个数N表示沙子的堆数N。
    第二行N个数,表示每堆沙子的质量(<=1000)。

    输出格式

    合并的最小代价

    样例输入

    4
    1 3 5 2

    样例输出

    22

    看完题,我们一种暴力的想法就是一个一个枚举,当然这样我们会理所当然的TLE(傻子都知道好吗!!

    这时,我们可以换个思路,我们假设两堆石子lr,当两堆石子可以被合并时,代表l到r中间的所有石子都已经被合并了,只有这样才能时l和r两堆石子相邻。

    且其中的石子数量是ri=lAi,所以,我们可以知道一定存在一个k(l≤k<r),可以使l到r中间的所有石子合并起来。即:

    在[l,k]的石子与[k+1,r]之间的石子合并,并最终得到[l,r]之间的石子。

    所以,我们就可以推出状态转移方程:

    1 for(int len=1;len<=n;len++){//表示阶段,表示每一次最多可以有几堆被合并 
    2         for(int l=1;l<=n-len+1;l++){//表示状态,左端点 
    3             int r=l+len-1;//表示右端点
    4             for(int k=l;k<r;k++){//决策点,又名中途转折点
    5                 f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);//状态转移方程 
    6             } 

    这样我们就解决这个问题,最后处理一下答案然后输出即可。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define maxn 307
     4 int f[maxn][maxn],n,sum[maxn],a[maxn];
     5 //template<typename __Type_of_scan>
     6 //void scan(__Type_of_scan &x){
     7 //    __Type_of_scan f=1;x=0;char s=getchar();
     8 //    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
     9 //    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    10 //    x*=f;
    11 //}
    12 int main(){
    13 //    scan(n);
    14     scanf("%d",&n);
    15 //    memset(sum,0,sizeof(sum));
    16     memset(f,0x3f,sizeof(f));//初始化 
    17     for(int i=1;i<=n;i++){
    18 //        scan(a[i]);
    19         scanf("%d",&a[i]);
    20         f[i][i]=0;//进行清零 
    21         sum[i]=sum[i-1]+a[i];//求前缀和 
    22     }
    23 //    for(int i=1;i<=n;i++){
    24 //        f[i][i]=0;
    25 //        sum[i]=sum[i-1]+a[i];
    26 //    }
    27     for(int len=1;len<=n;len++){//表示阶段,表示每一次最多可以有几堆被合并 
    28         for(int l=1;l<=n-len+1;l++){//表示状态,左端点 
    29             int r=l+len-1;//表示右端点
    30             for(int k=l;k<r;k++){//决策点,又名中途转折点
    31                 f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);//状态转移方程 
    32             } 
    33 //            f[l][r]+=sum[r]-sum[l-1];//更新每个点的权值 
    34         }
    35     }
    36 //    int minn=0x3f;
    37 //    int maxx=0;
    38 //    for(int i=1;i<=n;i++){
    39 //        minn=min(minn,f[i][n-i+1]);
    40 //        maxx=max(maxx,f[i][n-i+1]);
    41 //    } 
    42 //    printf("%d
    ",minn);
    43 //    printf("%d
    ",maxx);
    44     printf("%d",f[1][n]);
    45     return 0;
    46 }
    石子合并(非环)

    下面,我们来看一看加大难度的石子合并

    环形石子合并

    题目描述:

    题目描述

    在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

    试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

    输入输出格式

    输入格式:

    数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

    输出格式:

    输出共2行,第1行为最小得分,第2行为最大得分.

    输入输出样例

    输入样例#1:
    4
    4 5 9 4
    输出样例#1:
    43
    54

    提示一下:把环变成链即可

    代码:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define maxn 307
     4 int f1[maxn][maxn],n,sum[maxn],a[maxn],f2[maxn][maxn];
     5 //template<typename __Type_of_scan>
     6 //void scan(__Type_of_scan &x){
     7 //    __Type_of_scan f=1;x=0;char s=getchar();
     8 //    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
     9 //    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    10 //    x*=f;
    11 //}
    12 int main(){
    13 //    scan(n);
    14     scanf("%d",&n);
    15 //    memset(sum,0,sizeof(sum));
    16 //    memset(f2,0x3f,sizeof(f2));//最大值数组初始化 
    17     for(int i=1;i<=n;i++){
    18 //        scan(a[i]);
    19         scanf("%d",&a[i]);
    20         a[i+n]=a[i];//因为是环,所以要存两倍数 
    21 //        f2[i][i]=0;
    22 //        f1[i][i]=0;//进行清零 
    23 //        sum[i]=sum[i-1]+a[i];//求前缀和 
    24     }
    25     for(int i=1;i<=n*2;i++){
    26         f1[i][i]=0;
    27         f2[i][i]=0;
    28         sum[i]=sum[i-1]+a[i];}//求前缀和 
    29     for(int len=2;len<=n;len++){//表示阶段,表示每一次最多可以有几堆被合并 
    30         for(int l=1;l<=2*n-len;l++){//表示状态,左端点 
    31             int r=l+len-1;//表示右端点
    32             f2[l][r]=0x3f3f3f3f;//每一次p跑最小值时都要把这个点f2初始为最大
    33             for(int k=l;k<r;k++){//决策点,又名中途转折点,区间断点 
    34                 f1[l][r]=max(f1[l][r],f1[l][k]+f1[k+1][r]);//f1表示最大值状态转移方程 
    35                 f2[l][r]=min(f2[l][r],f2[l][k]+f2[k+1][r]);//f2表示最小值状态转移方程 
    36             } 
    37             f1[l][r]+=(sum[r]-sum[l-1]);//更新每个点的权值 
    38             f2[l][r]+=(sum[r]-sum[l-1]);
    39         }
    40     }
    41 //    for(int l=2;l<=n;l++){
    42 //        for(int p=1;p<=2*n-l;p++){
    43 //            int j=p+l-1;
    44 //            f1[p][j]=0;                //f1表示最大值,f2表示最小值 
    45 //            f2[p][j]=1926817;      //初始化最大最小值 
    46 //        for(int k=p;k<j;k++){   //k表示区间断点 
    47 //            f1[p][j]=max(f1[p][j],f1[p][k]+f1[k+1][j]);   // 
    48 //            f2[p][j]=min(f2[p][j],f2[p][k]+f2[k+1][j]);   // 
    49 //        } 
    50 //        f1[p][j]+=(sum[j]-sum[p-1]);
    51 //        f2[p][j]+=(sum[j]-sum[p-1]);
    52 //        }
    53 //    }
    54 //    int minn=0x3f;
    55 //    int maxx=0;
    56 //    for(int i=1;i<=n;i++){
    57 //        minn=min(minn,f1[i][n-i+1]);
    58 //        maxx=max(maxx,f2[i][n-i+1]);
    59 //    } 
    60     int dp1=0;
    61     int dp2=0x3f3f3f3f;
    62     for(int i=1;i<=n;i++){
    63         dp1=max(dp1,f1[i][i+n-1]);
    64         dp2=min(dp2,f2[i][i+n-1]);
    65     }
    66     printf("%d
    ",dp2);
    67     printf("%d
    ",dp1);
    68 //    printf("%d",f[1][n]);
    69     return 0;
    70 }
    石子合并(环形)

     于是,看完石子合并这个十分基础的题,我们来看一看一道poj1179

    这也是一道十分经典的区间dp的题,大家可以做一做,给点提示:把环变链再缩短,最后出答案(注意最大最小值的更新)。

    这道题的→_→  题解~~

    在给大家推荐一道题,是一道《算法进阶指南》的题,金字塔

    区间dp题目推荐(持续更新)

    洛谷P1063(能量项链)

    洛谷P1220(多维区间dp)

     

  • 相关阅读:
    go基础系列~定义新的数据类型
    go基础类型map类型
    redis基础系列~基础监控模板
    事务的隔离级别
    ASP.NET Core 中间件的几种实现方式
    asp.net core 认证和授权实例详解
    快速理解ASP.NET Core的认证与授权
    C# Hashtable、HashSet和Dictionary的区别
    C# 中使用 Redis 简单存储
    ASP.NET Core 中间件
  • 原文地址:https://www.cnblogs.com/xishirujin/p/10432858.html
Copyright © 2020-2023  润新知