以石子合并为例的区间DP

区间DP,是一类具有较为固定解法的DP,一般的思路都是:

first.初始化区间长度为1的情况(一般区间长度为1的较易于初始化)

second.

for(枚举区间长度2~n){
    for(枚举左端点){
     j=i+len-1//记录右端点
       for(枚举断点){
         //枚举断点后一般是比较以哪个断点分开最优(一般是比较最大或最小)
        }
    }
}

end.区间DP的特点:

合并:即将两个或多个部分进行整合,当然也可以反过来,也就是对一个问题分解成两个或多个部分。

特征:能将问题分解为两两合并的形式;

求解:对整个问题设最优值,枚举合并点,将问题分解成左右两部分,最后合并左右两个部分的最优值得到原问题的最优值。(显然无后效性啦)

然后我发现区间DP的数据范围都超级小,100左右


下面我萌以石子合并为例子来看具体的看区间DP:

石子合并【题目链接】

首先要说的是:这道题贪心是不对哒;

然后看正解:区间DP

如果第i堆石子与第j堆石子合并成一堆,说明i~j之间的所有石子也都被合并成了一堆,然后对于合并第i堆石子与第j堆石子的代价,可以看做是先将第i~k堆石子合并,再将第k+1~j堆石子合并的代价别忘记再加上Σ(k=i~j)val[k](val[i]表示第i堆石子的个数,显然不管你合并啥,只要合并第i堆石子~第j堆石子,一定需要加上第i堆石子到第j堆石子的和(感性理解一下qwq)),然后需要求最优的话,就是枚举i~j之间每一个断点k,取最优。

定义:sum[i]表示Σ(k=1~i)val[k](运用了前缀和的思想,如果要求区间i~j的和,可以用sum[j]-sum[i-1])

f_min[i][j]表示合并区间i~j所需要的最小花费;

f_max[i][j]表示合并区间i~j所需要的最大花费;

初始条件:f_min[i][i]=f_max[i][i]=0;(只合并自己一堆显然需要花费为0)

转移方程:

f_min[i][j]=min(f_min[i][j],f_min[i][k]+f_min[k+1][j])+sum[j]-sum[i-1];

f_max[i][j]=max(f_max[i][j],f_max[i][k]+f_max[k+1][j])+sum[j]-sum[i-1];

(i<=k<=j)

显然这个题需要枚举区间长度一层for,枚举区间起点一层for,枚举断点k一层for,然后就是三层for,时间复杂度O(n^3);


然后是对于环的处理,最好写好用的处理方法,把序列延长为原来的两倍。然后最后枚举一下取最优值就好了。

CODE:

#include<bits/stdc++.h>
#define ll long long
#define INF 2147483647

using namespace std;

inline int read(){
    int ans=0;
    char last=' ',ch=getchar();
    while(ch<'0'||ch>'9') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

int n,val[202];
int f_max[202][202],f_min[202][202],sum[202];

int main(){
    n=read();
    for(int i=1;i<=n;i++)
      val[i]=read(),val[i+n]=val[i];
    memset(f_min,0x3f,sizeof(f_min));
    for(int i=1;i<=2*n;i++){
        sum[i]=sum[i-1]+val[i];
        f_min[i][i]=0;f_max[i][i]=0;
    }
    for(int len=2;len<=n;len++){
        for(int i=1;i+len-1<=2*n;i++){
            int j=i+len-1;
            for(int k=i;k<=j;k++){
                f_min[i][j]=min(f_min[i][j],f_min[i][k]+f_min[k+1][j]);
                f_max[i][j]=max(f_max[i][j],f_max[i][k]+f_max[k+1][j]);
            }
            f_min[i][j]+=(sum[j]-sum[i-1]);
            f_max[i][j]+=(sum[j]-sum[i-1]);
        }
    }
    int ans_min=INF,ans_max=0;
    for(int i=1;i<=n;i++){
        ans_min=min(ans_min,f_min[i][i+n-1]);
        ans_max=max(ans_max,f_max[i][i+n-1]);
    }
    cout<<ans_min<<endl<<ans_max<<endl;
    return 0;
}

跑去做题(逃

end-

 

posted @ 2019-07-01 09:09  Sweetness  阅读(242)  评论(0编辑  收藏  举报