区间DP

区间DP

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

石子合并

题目链接:codevs石子合并

分析

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

code

#include
#define maxn 350
#define INF 2000000000
using 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//枚举所有的合并点。
        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
#define maxn 350
#define INF 2000000000
using 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;k1][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
#define maxn 120*2
#define INF 2000000000
using 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;k1][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; 
}

你可能感兴趣的:(dp,模板)