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

本文介绍了一道复杂的区间动态规划题目,详细阐述了如何定义状态和进行状态转移,并给出了具体实现代码。通过对数值进行离散化处理,最终解决了该问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值