区间DP

来源:互联网 发布:网络英语教育 编辑:程序博客网 时间:2024/06/06 01:20

区间DP

区间dp就是在一系列的区间中搞特一些子dp,昂。
这玩意儿还得用具体的题目来说。
下面展示一些模型。

石子合并

题目链接:codevs石子合并

分析

这道题目跟合并果子很像啊!但是这道题目只能合并相邻的两堆石子。怎么合并的总得分最大呢,就要保证每一次合并后,得分都最大。符合最优性原则,可以用我们神奇的DP做。
这道题目就是典型的一个区间DP。我们可以知道,每一个区间的最优解都是由更小的区间的最优解合并而来,所以我们划分阶段的依据就是区间长度。再枚举起点,算出终点,进行计算。

code

#include<bits/stdc++.h>#define maxn 350#define INF 2000000000using namespace std;inline int read(){    int num=0;    bool flag=true;    char c;    for(;c>'9'||c<'0';c=getchar())    if(c=='-')    flag=false;    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());    return flag ? num : -num;}//快读,背好就行。int n,a[maxn],f[maxn][maxn],sum[maxn];/*f[i][j]表示区间[I,j]的石头最小得分(最优解)。sum是一个前缀和数组,很好用。a数组是原数据。*/int main(){    n=read();    memset(f,0,sizeof(f)); //初始化    for(int i=1;i<=n;i++)    {        a[i]=read();        sum[i]=sum[i-1]+a[i];//处理前缀和    }    for(int len=2;len<=n;len++)    //枚举长度    for(int i=1;i<=n-len+1;i++)    //枚举起点    {        int j=i+len-1;        //求出终点        int temp=INF;        for(int k=i;k<j;k++)//枚举所有的合并点。        temp=min(temp,f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);//这就是我们著名的区间dp著名状态转移方程。        f[i][j]=temp;//求出最优解。    }    printf("%d\n",f[1][n]);//输出    return 0; }

石子合并++

题目链接:luogu石子合并

分析

这道题目跟上一题唯一的不一样就是这里的石头是环状排列的,这意味着我们需要进行一点不一样的预处理——断环为链。
方法就是——扩大两倍(看了代码就理解了)。

code

#include<bits/stdc++.h>#define maxn 350#define INF 2000000000using namespace std;inline int read(){    int num=0;    bool flag=true;    char c;    for(;c>'9'||c<'0';c=getchar())    if(c=='-')    flag=false;    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());    return flag ? num : -num;}//快读int n,a[maxn*2],sum[maxn*2];//a,原数组。sum:前缀和数组int f1[maxn*2][maxn*2],f2[maxn*2][maxn*2];//f1:放最小值;f2:放最大值int main(){    n=read();    for(int i=1;i<=n;i++)    {        a[i]=read();        a[n+i]=a[i];    }//著名的断环为链    for(int i=1;i<=2*n;i++)        sum[i]=sum[i-1]+a[i];    //当然两倍之后,前缀和也要搞两倍    for(int len=2;len<=n;len++)    for(int i=1;i<=2*n-len+1;i++)    //同上一题    {        int j=i+len-1;        int mx=-INF,mi=INF;        for(int k=i;k<j;k++)        {            mx=max(mx,f2[i][k]+f2[k+1][j]+sum[j]-sum[i-1]);            mi=min(mi,f1[i][k]+f1[k+1][j]+sum[j]-sum[i-1]);        }        f1[i][j]=mi;        f2[i][j]=mx;//一样的骚操作    }    int ansmin=INF,ansmax=-INF;    for(int i=1;i<=n;i++)        {            ansmin=min(ansmin,f1[i][i+n-1]);            ansmax=max(ansmax,f2[i][i+n-1]);        }    /*由于是原题是环,意味着我们这里有不一样的操作,    我们需要枚举不同的断开点*/    printf("%d\n%d",ansmin,ansmax);    return 0; }

能量项链

题目链接:luogu能量项链

分析

这道题目跟合并果子实在是太像了。只要处理一下计算收益的部分就OK了。

#include<bits/stdc++.h>#define maxn 120*2#define INF 2000000000using namespace std;inline int read(){    int num=0;    bool flag=true;    char c;    for(;c>'9'||c<'0';c=getchar())    if(c=='-')    flag=false;    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());    return flag ? num : -num;}int f[maxn][maxn],n;struct node {    int head,tail;}a[maxn];int main(){    n=read();    for(int i=1;i<=n;i++)    a[i].head=a[i+n].head=read();    for(int i=1;i<=2*n-1;i++)        a[i].tail=a[i+1].head;    a[2*n].tail=a[1].head;    for(int len=2;len<=n;len++)    for(int i=1;i<=2*n-len+1;i++)    {        int j=i+len-1;        int temp=-INF;        for(int k=i;k<j;k++)        {            temp=max(temp,f[i][k]+f[k+1][j]+a[i].head*a[k].tail*a[j].tail);        }        f[i][j]=temp;    }    int ans=-INF;    for(int i=1;i<=n;i++)    ans=max(ans,f[i][i+n-1]);    printf("%d",ans);    return 0; }
原创粉丝点击