dp day2/3-区间dp

来源:互联网 发布:淘宝网西服 编辑:程序博客网 时间:2024/05/19 19:13

区间dp

区间dp一般都是考虑对于每段区间,最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,

将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合 ,求合并后的最优值。

道理我都懂,但是做了几题之后,发现最难的一点是要看出这题是个区间dp......

这需要一些转化,需要积累......

然后是要找到阶段、状态和状态转移方程。

洛谷p1880石子合并

环形的区间dp,是一个模版,具体看代码吧......

#include<bits/stdc++.h>using namespace std;int n,a[210]={0},s[210][210]={0},ma=0,mi=1000000,sum[210]={0};int main(){scanf("%d",&n);for(int i=1;i<=n;i++){    scanf("%d",&a[i]);    a[i+n]=a[i];//环形,所以乘二}for(int i=1;i<=n*2;i++)sum[i]=sum[i-1]+a[i];//前缀和 for(int i=1;i<=n;i++)//先搜最大值(否则就听取WA声一片啦)i表示合并的区间长度for(int j=1;j<=n*2;j++)//j表示区间起点{    int end=j+i-1;if(end>2*n)break;    for(int k=j;k<end;k++)//枚举断开点    s[j][end]=max(s[j][end],s[j][k]+s[k+1][end]+sum[end]-sum[j-1]);}//sum即这次移动所加值 for(int i=1;i<=n;i++)if(s[i][i+n-1]>ma)ma=s[i][i+n-1];for(int i=1;i<=n;i++)//最小值 for(int j=1;j<=n*2;j++){    int end=j+i-1;if(end>2*n)break;    for(int k=j;k<end;k++)    s[j][end]=min(s[j][end],s[j][k]+s[k+1][end]+sum[end]-sum[j-1]);}for(int i=1;i<=n;i++)if(s[i][i+n-1]<mi)mi=s[i][i+n-1];printf("%d\n%d",mi,ma);return 0;}
洛谷p1063能量项链

也是区间dp,思想很相似

#include<bits/stdc++.h>using namespace std;long long a[210],t,s[210][210]={0},ma=0,mi=1000000,sum[210]={0};int main(){scanf("%d",&t);for(int i=1;i<=t;i++){    scanf("%d",&a[i]);    a[i+t]=a[i];}for(int i=1;i<=t;i++)//依旧枚举区间长度for(int j=1;j<=t*2;j++)//枚举起始点{    int end=j+i-1;if(end>2*t)break;    for(int k=j;k<end;k++)//枚举断开点    s[j][end]=max(s[j][end],s[j][k]+s[k+1][end]+a[j]*a[k+1]*a[end+1]);//选择最优解}for(int i=1;i<=t;i++)if(s[i][i+t-1]>ma)ma=s[i][i+t-1];printf("%lld",ma);return 0;}

洛谷p1220关路灯

已经关闭的灯一定在一个连续区间里,这很显然。如果a和b是不连续的两盏灯,被灯c分开,那么关了a再去关b的时候可以顺带把c关掉。

然后发现,在关闭了一个连续的区间的灯之后,人一定在端点。

所以用s[i][j][0]表示关第i盏到第j盏路灯后人处于第i盏路灯时的最小值,s[i][j][1]表示人处于第j盏灯时的最小值。

就可以区间dp啦。

#include<bits/stdc++.h>using namespace std;int s[60][60][2],n,c;struct light{int p,w;}l[60];inline void init(){scanf("%d%d",&n,&c);l[0].w=0;for(int i=1;i<=n;i++){    scanf("%d%d",&l[i].p,&l[i].w);    l[i].w+=l[i-1].w;//前缀和}return;}inline void dp(){for(int i=1;i<=n;i++)s[i][i][0]=s[i][i][1]=l[n].w*abs(l[i].p-l[c].p);//初始值 for(int len=2;len<=n;len++)for(int i=1;i<=n-len+1;i++){int j=i+len-1;//最后关第i盏灯则之前已关第i+1至第j盏灯,最后关第j盏灯反之s[i][j][0]=min(s[i+1][j][0]+abs(l[i+1].p-l[i].p)*(l[i].w+l[n].w-l[j].w),//加上的为关灯途中没关的灯所耗的电能               s[i+1][j][1]+abs(l[j].p-l[i].p)*(l[i].w+l[n].w-l[j].w));s[i][j][1]=min(s[i][j-1][1]+abs(l[j].p-l[j-1].p)*(l[i-1].w+l[n].w-l[j-1].w),               s[i][j-1][0]+abs(l[j].p-l[i].p)*(l[i-1].w+l[n].w-l[j-1].w));}return;}int main(){init();dp();if(s[1][n][0]<s[1][n][1])printf("%d",s[1][n][0]);else printf("%d",s[1][n][1]);return 0;}
洛谷p1622释放囚犯

正着做死活做不出,参考题解中所说,将这个过程反过来,把要释放的人视作断点,将p个人分成q+1个区间,求合并区间至一个区间所需最小值,即可视为合并石子,突然通悟......

#include<bits/stdc++.h>using namespace std;int s[110][110]={0},p,q,a[110]={0},sum[110]={0};inline void init(){scanf("%d%d",&p,&q);for(int i=1;i<=q;i++)scanf("%d",&a[i]);a[0]=0;a[++q]=p+1;        sort(a,a+q+1);return;}int main(){    init();    for(int i=1;i<=q;i++)    sum[i]=a[i]-a[i-1]-1+sum[i-1];//前缀和,将问题转换为求几堆石子合并的最小值    for(int k=2;k<=q;k++)    for(int i=1;i<=q-k+1;i++)    {        int j=i+k-1;        for(int p=i;p<j;p++)        if(!s[i][j]||s[i][j]>s[i][p]+s[p+1][j]+sum[j]-sum[i-1]+j-i-1)//注意j-i+1,是指合并时几个还未释放的人        s[i][j]=s[i][p]+s[p+1][j]+sum[j]-sum[i-1]+j-i-1;    }    printf("%d",s[1][q]);    return 0;}

洛谷p3205合唱队伍

先将问题转化为求排成末状态的不同的方案数。

对于最终序列的一个区间[i,j],最后一个添加的只可能是第i个人或者第j个人。如果最后一个是i,则对于这个区间,a[i]<a[i+1]或者a[i]<a[j]。如果最后一个是j,则对于这个区间a[j]>a[j-1]或者a[j]>a[i]。

#include<bits/stdc++.h>using namespace std;int n,s[1010][1010][2]={0},a[1010]={0};s[i][j][0]表示排出[i,j]且最后一个人从左边排入的方案数,s[i][j][1]表示最后一人从右边排入const int mod=19650827;inline void init(){scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&a[i]);for(int i=1;i<=n;i++)s[i][i][0]=1;//s[i][i][1]不能也存1,否则结果会doublereturn;}inline void dp(){for(int len=2;len<=n;len++)for(int i=1;i<=n-len+1;i++){int j=i+len-1;if(a[i]<a[i+1])s[i][j][0]+=s[i+1][j][0];//如果可从左侧排入人if(a[i]<a[j])s[i][j][0]+=s[i+1][j][1];if(a[j]>a[i])s[i][j][1]+=s[i][j-1][0];//如果可从右侧排入人if(a[j]>a[j-1])s[i][j][1]+=s[i][j-1][1];s[i][j][0]%=mod;s[i][j][1]%=mod;}return;}int main(){init();dp();printf("%d",(s[1][n][0]+s[1][n][1])%mod);return 0;}


原创粉丝点击