【JZOJ5620】【NOI2018模拟4.1】修炼(DP+斜率优化)

Problem

  小X要进行D天的修炼,初始时他有C点魂力。有N件魂导器,第i件会在第Di天以Pi点魂力的价格售出,他有它时则可以用它修炼,每天获得Gi点魂力,他可以以Ri点魂力的代价卖掉它。小X魂力不足Pi不能买。小X有买卖的日子里不能修炼。D天结束后,小X卖掉手头的魂导器。求最后至多能有多少魂力。

Input

这里写图片描述

Output

这里写图片描述

Hint

这里写图片描述

Solution

  首先我一看题:魂力、魂导器……这不是斗罗大陆吗?!(什么时候魂导器还能用魂力交易了!!!)
  那么这题一眼DP了。

30points:DP

  容易想到以d为关键字,将魂导器从小到大排序,然后DP。
  设f[i]表示到达第d[i]天且手中没有魂导器时的最大魂力。那么有边界:f[0]=C,d[0]=0,d[n+1]=D+1。那么极易推出朴素的转移:

f [ i ] = max j = 0 i 1 f [ j ] p [ j ] + r [ j ] + g [ j ] ( d [ i ] d [ j ] 1 )

  时间复杂度: O ( n 2 )

Code

  代码就不贴了,反正下面的60points做法也有。

60points:斜率优化

  我们瞟到4、5、6这仨点有个特殊性质:对于任意i、j,若Di≥Dj,那么Gi≥Gj。而且基于这种题一般都会用斜率优化,那么我们不妨也推一推。
  首先,沿袭30points做法的状态设法。我们假设j<k<i,且由f[j]转移到f[i]较f[k]转移到f[i]更优。从f[j]转移到f[i]的式子可化简为:

f [ j ] p [ j ] + r [ j ] g [ j ] ( d [ j ] + 1 ) + g [ j ] d [ i ]

  容易发现前面红色的部分只与j有关,不妨设其为v[j],则由f[j]转移到f[i]较f[k]转移到f[i]更优就是要满足:
v [ j ] + g [ j ] d [ i ] > v [ k ] + g [ k ] d [ i ]

  移项可得:
g [ j ] d [ i ] g [ k ] d [ i ] > v [ k ] v [ j ]

  哈哈,由于此时的j<k,d[j]就≤d[k],g[j]也自然而然≤g[k],可以确定其正负,可以从容约分:
d [ i ] v [ k ] v [ j ] g [ j ] g [ k ]

  右边即为j、k两点间直线的斜率,不妨设其为G(j,k)。那么当d[i]≤G(j,k)时,j比k优。
  那么若有三点j1,k1,k2,且它们满足G(j1,k1)≥G(k1,k2),则说明当k1比k2优时,j1肯定也比k1优,我们可删去k1这个点。这样我们就需维护一个单调不减的斜率。
  注意到d[i]是单调不减的,所以我们可以不必二分,直接取队首转移。当然,要注意弹栈,且计算斜率时g[j]可能=g[k],要注意特判。
  最后,友情提醒:1. 计算完f[i]后,要判断f[i]足不足以支持它买下第i个魂导器,足以才能拿它转移其他的;2.由于条件是“若Di≥Dj,那么Gi≥Gj”,那么Di可能=Dj,所以 务必要拿G做排序的第二关键字。我就被这两点坑了好久。
  时间复杂度:快排 O ( n l o g 2 n ) ,DP O ( n )

Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define Lf long double
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
const int N=1e5+10;
int i,j,k,num,q,n,sta[N],head,tail;
Lf c,d,C,f[N],v[N];
struct hdq
{
    Lf d,p,r,g,s;
}a[N];
inline bool compare(hdq a,hdq b)
{
    return a.d<b.d||a.d==b.d&&a.g<b.g;
}

inline Lf G(int j,int k)
{
    return (v[k]-v[j])/(a[j].g-a[k].g);
}
inline bool cmp(int j1,int k1,int k2)
{
    if(a[j1].g==a[k1].g)return v[k1]-v[j1]<0;
    if(a[k1].g==a[k2].g)return v[k2]-v[k1]>=0;
    return G(j1,k1)>=G(k1,k2);
}
void work()
{
    memset(f,0,sizeof f);
    f[0]=v[0]=c;
    a[n+1].d=d+1;
    if(num<4)
    {
        fo(i,1,n+1)
            fo(j,0,i-1)
                if(f[j]>=a[j].p)
                    f[i]=max(f[i],f[j]+a[j].s+a[j].g*a[i].d);
        return;
    }
    sta[head=tail=1]=0;
    fo(i,1,n+1)
    {
        while(head<tail)
        {
            j=sta[head];k=sta[head+1];
            if(a[i].d*(a[j].g-a[k].g)<=v[k]-v[j])
                    head++;
            else    break;
        }
        j=sta[head];
        f[i]=v[j]+a[j].g*a[i].d;
        if(f[i]>=a[i].p)
        {
            v[i]=f[i]+a[i].s;
            while(head<tail&&cmp(sta[tail-1],sta[tail],i))tail--;
            sta[++tail]=i;
        }
    }
}
int main()
{
    freopen("practice.in","r",stdin);
    freopen("practice.out","w",stdout);
    for(scanf("%d%d",&num,&q);q;q--)
    {
        scanf("%d%Lf%Lf",&n,&c,&d);
        memset(a,0,sizeof a);
        fo(i,1,n)
        {
            scanf("%Lf%Lf%Lf%Lf",&a[i].d,&a[i].p,&a[i].r,&a[i].g);
            C=a[i].r-a[i].p;
            a[i].s=C-a[i].g*(1+a[i].d);
        }
        sort(a+1,a+n+1,compare);

        work();

        printf("%.0Lf\n",f[n+1]);
    }
}

100points:排序关键字

  上述做法都是拿d作关键字,其实我们也可以拿g作关键字,因为我们肯定是从g小的魂导器转移到g大的魂导器。转移和斜率优化与60points方法一致,不同之处就在于将g的不单调转变成了d的不单调。那么,转移时就二分一下即可。
  时间复杂度: O ( n l o g 2 n )

Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define Lf long double
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
const int N=1e5+10;
int i,j,k,num,q,n,sta[N],top;
Lf c,d,C,f[N],v[N];
struct hdq
{
    Lf d,p,r,g,s;
}a[N];
inline bool compare(hdq a,hdq b)
{
    return a.g<b.g;
}

inline bool better(int j,int k)
{
    return a[i].d*(a[j].g-a[k].g)>v[k]-v[j];
}
inline Lf G(int j,int k)
{
    return (v[k]-v[j])/(a[j].g-a[k].g);
}
inline bool cmp(int j1,int k1,int k2)
{
    if(a[j1].g==a[k1].g)return v[k1]-v[j1]<0;
    if(a[k1].g==a[k2].g)return v[k2]-v[k1]>=0;
    return G(j1,k1)>=G(k1,k2);
}
int bs(int l,int r)
{
    int mid;
    while(l<r)
    {
        mid=(l+r+1)>>1;
        if(better(sta[mid-1],sta[mid]))
                r=mid-1;
        else    l=mid;
    }
    return sta[l];
}
void work()
{
    memset(f,0,sizeof f);
    f[0]=v[0]=c;
    a[n+1].d=d+1;
    sta[top=1]=0;
    fo(i,1,n+1)
    {
        j=bs(1,top);
        f[i]=v[j]+a[j].g*a[i].d;
        if(f[i]>=a[i].p)
        {
            v[i]=f[i]+a[i].s;
            while(top>1&&cmp(sta[top-1],sta[top],i))top--;
            sta[++top]=i;
        }
    }
}
int main()
{
    freopen("practice.in","r",stdin);
    freopen("practice.out","w",stdout);
    for(scanf("%d%d",&num,&q);q;q--)
    {
        scanf("%d%Lf%Lf",&n,&c,&d);
        memset(a,0,sizeof a);
        fo(i,1,n)
        {
            scanf("%Lf%Lf%Lf%Lf",&a[i].d,&a[i].p,&a[i].r,&a[i].g);
            C=a[i].r-a[i].p;
            a[i].s=C-a[i].g*(1+a[i].d);
        }
        sort(a+1,a+n+1,compare);

        work();

        printf("%.0Lf\n",f[n+1]);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/79835607