【BZOJ4897】成绩单(THUSC2016)-玄幻区间DP

测试地址:成绩单
做法:本题需要用到区间DP。
容易想到每次取的都是一个子序列。直觉上想到的一个状态定义是,令f(i,j)为删掉区间[i,j]中所有数的最小代价,但我们发现这没法转移,又注意到转移和所取的子序列中最大数和最小数有关,那么便有了如下状态定义和状态转移方程:
f(i,j,l,r)为删掉区间[i,j]中的一部分数,使得剩下的数都在[l,r]范围内的最小代价,g(i,j)为删掉区间[i,j]中所有数的代价,则有:
f(i,j,l,r)=min(g(tl,tr),f(i,k,l,r)+f(k+1,j,l,r)(ik<j))
其中tl,tr为区间[l,r]中从左边/右边开始数第一个不在[l,r]内的数的位置。这个方程通过一种玄幻的方式枚举了所有可能是最优的方案,可以感性理解一下……
然后我们有:
g(i,j)=min(f(i,j,l,r)+a+b(rl)2)
这个方程就很显然了。那么我们把所有数离散化后做DP,就可以解决这一题了,时间复杂度为O(n5)
(这题真的好难啊……为什么还有dalao说是DP入门题啊……可能是国家队入门吧)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000ll*1000000000ll;
int n,now[55];
ll a,b,pos[55],f[55][55][55][55],g[55][55];
struct forsort
{
    int id;
    ll val;
}F[100];

bool cmp(forsort a,forsort b)
{
    return a.val<b.val;
}

int main()
{
    scanf("%d%lld%lld",&n,&a,&b);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&F[i].val);
        F[i].id=i;
    }

    sort(F+1,F+n+1,cmp);
    int tot=0;
    for(int i=1;i<=n;i++)
    {
        if (i==1||F[i].val!=F[i-1].val) pos[++tot]=F[i].val;
        now[F[i].id]=tot;
    }

    for(int i=1;i<=n;i++)
    {
        g[i][i]=a;
        for(int l=1;l<=tot;l++)
            for(int r=l;r<=tot;r++)
            {
                if (now[i]>=l&&now[i]<=r) f[i][i][l][r]=0;
                else f[i][i][l][r]=a;
            }
    }
    for(int len=2;len<=n;len++)
        for(int i=1;i+len-1<=n;i++)
        {
            int j=i+len-1,maxv=0,minv=tot+1;
            for(int x=i;x<=j;x++)
                maxv=max(maxv,now[x]),minv=min(minv,now[x]);
            g[i][j]=a+b*(pos[maxv]-pos[minv])*(pos[maxv]-pos[minv]);
            for(int l=1;l<=tot;l++)
                for(int r=l;r<=tot;r++)
                {
                    int tl=i-1,tr=j+1;
                    for(int k=i;k<=j;k++)
                    {
                        if (now[k]>=l&&now[k]<=r) tl=k;
                        else break;
                    }
                    for(int k=j;k>=i;k--)
                    {
                        if (now[k]>=l&&now[k]<=r) tr=k;
                        else break;
                    }
                    if (tl>=tr) f[i][j][l][r]=0;
                    else f[i][j][l][r]=g[tl+1][tr-1];
                    for(int k=i;k<j;k++)
                        f[i][j][l][r]=min(f[i][j][l][r],f[i][k][l][r]+f[k+1][j][l][r]);
                }
            for(int l=1;l<=tot;l++)
                for(int r=l;r<=tot;r++)
                    g[i][j]=min(g[i][j],f[i][j][l][r]+a+b*(pos[r]-pos[l])*(pos[r]-pos[l]));
        }
    printf("%lld",g[1][n]);

    return 0;
}
posted @ 2018-05-23 22:37  Maxwei_wzj  阅读(129)  评论(0编辑  收藏  举报