Codeforces 392E Deleting Substrin(区间dp)

题目大意:

• 给定vi,wi,每次可以在wi中选择一个子段[l,r]满足:
• |wi-wi+1|=1 (l<=i<r)
• 2wi-wi-1-wi+1>=0 (l<i<r)
• 选择后获得vr-l+1的收益并把这个子段删除,可以不删完,求最大收益。
• n<=400

Examples
input
3
0 0 3
1 2 1
output
3
input
6
1 4 5 6 7 1000
2 1 1 2 2 3
output
12

题解:
我们删除的区间是一个单峰上凸的区间。
用f[i][j]表示将i到j全部删完得到的最大代价,g[i][j]表示将i到j的区间删成w[i],w[i]+1,……,w[j]的最大收益,h[i][j]
表示将i到j的区间删成w[i],w[i]-1,……,w[j]的最大收益。
f[i][j]可以由f[i][k]+f[k+1][j]||g[i][k]+h[k][j]+v[2*w[k]-w[i]-w[j]+1]转移过来。
h[i][j]和g[i][j]怎么维护呢? 当w[k]==w[i+1]时g[i][j]<-f[i+1][k-1]+g[k][j],h[i][j]同理。
代码:
#include<cstdio>
#include<algorithm>
#define inf 1<<28
using namespace std;
template<class T>inline void read(T &x)
{
    x=0;int f=0;char ch=getchar();
    while(ch<'0'||ch>'9')f|=(ch=='-'),ch=getchar();
    while(ch<='9'&&ch>='0')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=f?-x:x;
    return;
}
int n,v[410],w[410],f[410][410],h[410][410],g[410][410],ans[410];
int main()
{
    read(n);
    for(int i=1;i<=n;i++)read(v[i]);
    for(int i=1;i<=n;i++)read(w[i]);
    for(int l=1;l<=n;l++)
    {
        for(int i=1;i+l-1<=n;i++)
        {
            int j=i+l-1;
            if(i==j){g[i][j]=h[i][j]=0;f[i][j]=v[1];continue;}
            g[i][j]=h[i][j]=-inf;
            for(int k=i+1;k<=j;k++)
            if(w[k]==w[i]+1)g[i][j]=max(g[i][j],f[i+1][k-1]+g[k][j]);
            else if(w[k]==w[i]-1)h[i][j]=max(h[i][j],f[i+1][k-1]+h[k][j]);
            f[i][j]=-inf;
            for(int k=i;k<j;k++)
            f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
            for(int k=i;k<=j;k++)
            if(g[i][k]>-1<<20&&h[k][j]>-1<<20)f[i][j]=max(f[i][j],g[i][k]+h[k][j]+v[2*w[k]-w[i]-w[j]+1]);
        }
    }
    for(int i=1;i<=n;i++)
    {
        ans[i]=ans[i-1];
        for(int j=1;j<=i;j++)
        ans[i]=max(ans[i],ans[j-1]+f[j][i]);
    }
    printf("%d
",ans[n]);
    return 0;
}




原文地址:https://www.cnblogs.com/jiangtao0508/p/7742100.html