区间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\n",minn);
43 //    printf("%d\n",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\n",dp2);
67     printf("%d\n",dp1);
68 //    printf("%d",f[1][n]);
69     return 0;
70 }
石子合并(环形)

 

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

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

这道题的→_→  题解~~

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

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

洛谷P1063(能量项链)

洛谷P1220(多维区间dp)

 

 

 

 

posted @ 2019-02-25 19:32  惜时如金  阅读(152)  评论(0编辑  收藏  举报