JZOJ5620. 【NOI2018模拟4.1】修炼

本文详细解析了一道经典的斜率优化动态规划问题,通过逐步优化算法复杂度的方式,最终达到高效的解决方案。文章提供了完整的代码实现,并针对不同阶段的优化策略进行了深入浅出的解释。

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

题目描述

这里写图片描述
这里写图片描述
这里写图片描述

懒得改
奥妙重重的题面

10%

暴力?
O(2n)

30%

Dp,按照时间排序
设f[i]表示当前买第i个魂导器的最大收益。
f[i]=max(f[i],f[j]+r[j]+(d[i]d[j]1)g[j])p[i]
O(n2)

40%

奇怪的水法优化
维护一个队列,满足头和尾不会出现一种方案完全优于前/后一种方案
O(n2)

60%

因为D[i]>D[j]时,G[i]>G[j]
那么按照时间d来排序,那么每次从时间前的转移到时间后的一定最优
斜率优化乱搞
具体看https://blog.youkuaiyun.com/qq_36551189/article/details/79835607
O(nlogn)

70%

伪·斜率优化
不会
O()

100%

60分的做法是按照时间d来排序,实际上可以按g来排序。
那么从前转移到后可以保证g单调最优。

因为
f[i]=max(f[i],f[j]+r[j]+(d[i]d[j]1)g[j])p[i]
f[i]=max(f[i], f[j]+r[j]-d[j]*g[j]-g[j]+ d[i]g[j])p[i]
所以设标红部分为G[i]
f[i]=max(f[i],G[i]+d[i]g[j])p[i]

设当前i有j、k两个状态可以转移过来(j< k)
如果k比j优
digj+Gj<digk+Gk
di(gjgk)<GkGj
di>GkGjgjgk
(因为是按g来排序)

因为di 不单调,所以每次用二分来找
可以看出维护的是一个下凸壳,每次只用删去尾部

那么当 xl(dt1,dt)>xl(dt,i) 时便不满足下凸壳的性质,删去尾部

code

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define max(a,b) (a>b?a:b)
using namespace std;

long long d[100001];
long long p[100001];
long long r[100001];
long long g[100001];
long long f[100001];
long long G[100001];
long long _d[100001];
long long n,C,D,Q,i,j,k,l,ans,t,L,R,mid;

void swap(long long &a,long long &b) {long long c=a;a=b;b=c;}

void qsort(long long L,long long R)
{
    long long i,j,mid;

    i=L;
    j=R;
    mid=g[(L+R)/2];

    while (i<=j)
    {
        while (g[i]<mid) i++;
        while (g[j]>mid) j--;

        if (i<=j)
        {
            swap(d[i],d[j]);
            swap(p[i],p[j]);
            swap(r[i],r[j]);
            swap(g[i],g[j]);

            i++,j--;
        }
    }

    if (L<j) qsort(L,j);
    if (i<R) qsort(i,R);

    return;
}

double xl(int j,int k) {return double((G[k]-G[j])/(g[j]-g[k]));}

int main()
{
    freopen("practice.in","r",stdin);
    freopen("practice.out","w",stdout);

    scanf("%lld%lld",&i,&Q);

    for (;Q;Q--)
    {
        memset(f,128,sizeof(f));

        scanf("%lld%lld%lld",&n,&f[0],&D);
        fo(i,1,n)
        scanf("%lld%lld%lld%lld",&d[i],&p[i],&r[i],&g[i]);

        qsort(1,n);

        t=1,_d[1]=0;
        G[0]=f[0];
        fo(i,1,n)
        {
            L=2;
            R=t;
            while (L<R)
            {
                mid=(L+R)/2;

                if (xl(_d[mid-1],_d[mid])<d[i])
                L=mid+1;
                else
                R=mid;
            }
            if (t>1)
            {
                if (xl(_d[L-1],_d[L])>=d[i]) L--;
            }
            else
            L=1;

            j=_d[L];
            f[i]=d[i]*g[j]+G[j]-p[i];

            if (f[i]>=0)
            {
                G[i]=f[i]+r[i]-d[i]*g[i]-g[i];

                if (t && g[_d[t]]==g[i])
                {
                    if (G[i]>G[_d[t]]) t--;
                    else continue;
                }

                while (t>1 && xl(_d[t-1],_d[t])>xl(_d[t],i))
                t--;

                _d[++t]=i;
            }
        }

        ans=0;
        fo(i,0,n)
        if (f[i]>=0)
        ans=max(ans,f[i]+r[i]+(D-d[i])*g[i]);

        printf("%lld\n",ans);
    }

    fclose(stdin);
    fclose(stdout);

    return 0;
}

后记

作为一个万年写不对斜率优化的OIer居然一次过了2333

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值