【BZOJ】3963: [WF2011]MachineWorks

【题意】给定n台在时间di可以买入的机器,pi买入,可在任意时间ri卖出,买入和卖出之间的持有时间每天产生gi金钱,任意时间至多持有一台机器。给定初始钱数c和总天数T,求最大收益。n<=10^5。

【算法】动态规划+斜率优化(CDQ分治)

【题解】机器按di排序,添加一台时间为T+1的机器,令f[i]表示di时不持有机器(最后一台机器在di之前卖出)的最大收益。

f[i]=max{ f[i-1] , f[j]-p[j]+r[j]+g[j]*(d[i]-d[j]-1) } , j<i

为了简化方程,令A[i]=f[j]-p[j]+r[j]-g[j]*(d[j]+1),则

f[i]=max{ f[i-1] , A[j]+g[j]*d[i] } , j<i

对于g[j]<g[k]的两个决策j和k,当k优于j时满足:

A[j]+g[j]*d[i]<A[k]+g[k]*d[i] 即 (A[j]-A[k])/(g[j]-g[k])>-d[i]

用CDQ分治维护上凸包,先按-d[i]排序,然后左子区间构造凸包(按g[]排序),右子区间顺序决策(按-d[i]排序),最后按x[]归并排序。

具体过程见CDQ分治维护斜率优化

复杂度O(n log n)。

#include<cstdio>
#include<cctype>
#include<algorithm>
#define ll long long
using namespace std;
int read(){
    char c;int s=0,t=1;
    while(!isdigit(c=getchar()))if(c=='-')t=-1;
    do{s=s*10+c-'0';}while(isdigit(c=getchar()));
    return s*t;
}
const int maxn=100010;
const double eps=1e-10,inf=1000000000000;
int s[maxn],n,m;
ll f[maxn];
struct cyc{int d,p,r,g,id,x;ll y;}a[maxn],b[maxn];
bool cmp(cyc a,cyc b){return -a.d<-b.d;}
double slope(int A,int B){
    if(a[A].x==a[B].x){if(a[B].y<a[A].y)return inf;else return -inf;}
    return 1.0*(a[A].y-a[B].y)/(a[A].x-a[B].x);
}
void CDQ(int l,int r){
    if(l==r){
        f[l]=max(f[l-1],f[l]);
        a[l].x=a[l].g;
        a[l].y=f[l]-a[l].p+a[l].r-1ll*a[l].g*(a[l].d+1);
        if(f[l]<a[l].p)a[l].g=-1;
        return;
    }
    int mid=(l+r)>>1;
    int x1=l-1,x2=mid;
    for(int i=l;i<=r;i++)if(a[i].id<=mid)b[++x1]=a[i];else b[++x2]=a[i];
    for(int i=l;i<=r;i++)a[i]=b[i];
    CDQ(l,mid);
    int top=0;
    for(int i=l;i<=mid;i++)if(~a[i].g){
        while(top>1&&slope(s[top],i)<slope(s[top-1],s[top]))top--;
        s[++top]=i;
    }
    int x=1;
    for(int i=mid+1;i<=r;i++){
        while(x<top&&slope(s[x],s[x+1])<-a[i].d)x++;
        if(x<=top)f[a[i].id]=max(f[a[i].id],a[s[x]].y+1ll*a[s[x]].g*a[i].d);
    }
    CDQ(mid+1,r);
    x1=l,x2=mid+1;
    for(int i=l;i<=r;i++){
        if(x1==mid+1)b[i]=a[x2++];else
        if(x2==r+1)b[i]=a[x1++];else
        if(a[x1].g>a[x2].g)b[i]=a[x1++];else b[i]=a[x2++];
    }
    for(int i=l;i<=r;i++)a[i]=b[i];
}
int main(){
    n=read();f[0]=read();m=read();
    int T=0;
    while(n||f[0]||m){
        T++;
        for(int i=1;i<=n;i++)a[i].d=read(),a[i].p=read(),a[i].r=read(),a[i].g=read();
        sort(a+1,a+n+1,cmp);
        for(int i=1;i<=n;i++)a[i].id=n-i+1;
        a[++n]=(cyc){m+1,0,0,0,n,0,0};
        sort(a+1,a+n+1,cmp);
        for(int i=1;i<=n;i++)f[i]=0;
        CDQ(1,n);
        printf("Case %d: %lld\n",T,f[n]);
        n=read();f[0]=read();m=read();
    }
    return 0;
}
View Code

代码中使用的方程时(A[j]-A[k])/(g[j]-g[k])<-d[i] ,g[j]>g[k]。

转载于:https://www.cnblogs.com/onioncyc/p/8253066.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值