hdu 3842 Machine Works

2 篇文章 0 订阅
1 篇文章 0 订阅

题目链接

题目大意

有一些机器,可以在 di 时将它买入,价格为 pi ,在一段时间后可以以 ri 的价格卖出,机器运作时每天收入为 gi
机器在买入和卖出当天不可以运作,但是可以在同一天买入一台机器并卖出另一台机器。
厂房里只能有一台机器。
在n+1天时,你必须卖掉手头的机器。
。。。
还有一堆条件,详见题目。

题解

这是一道cdq分治优化dp的题目,和cash那题差不多。
首先推出dp方程:
定义dp[i]表示在第 di 天时卖掉手头的机器后的钱数。
那么可以知道
dp[i]=Max{dp[i1],dp[j]p[j]+r[j]+(d[i]d[j])g[j]}
条件是dp[j]>p[j]
可以先假定 A[i]=dp[i]p[i]+r[i]
那么,对于两个数j,k如果j比k优,就有
A[j]+(d[i]d[j])g[j]>A[k]+(d[i]d[k])g[k]
通过移项,可以将关于i的限制条件换到一边,于是得到:
B[i]=A[i](D[i]+1)G[i]
Di<BkBjGjGk
同时可推出新的dp方程 B[j]=d[i]g[j]+dp[i]
可以将(g[j],B[j])看做平面上的点,答案就是求过一些点,且斜率为-d[i]的直线中,截距的最大值。
于是可以维护一个凸包,然后更新答案。
维护这个凸包要先按g排序,然后在维护,但是dp过程中不可能每次都sort一遍,所以这时候就需要cdq分治。
cdq分治,简而言之,就是首先递归计算左边一半的值,然后利用左边一半的值去更新右边一半。
于是问题就变得明朗了,每次先把左边一半递归计算,然后把左边一半维护成一个凸包,然后更新右边一半的dp值。
由于题目要求最后一天必须卖掉手头的机器,可以虚设一个n+1点,答案就是dp[n+1]。
同时可以虚设一个0结点作为开始。

const int M=100005;
#define fi first
#define se second
typedef long long ll;
typedef pair<int,ll> pii;
inline void rd(int&res){
    res=0;char c;
    while(c=getchar(),c<48);
    do res=res*10+(c^48);
    while(c=getchar(),c>47);
}
struct node{
    int d,p,r,g;
    //卖出时间,价格,折扣价.利润
    void input(){
        rd(d);rd(p);rd(r);rd(g);
    }
}mac[M];
ll dp[M];
bool cmp(node a,node b){
    return a.d<b.d;
}
inline ll calc(int x){
    return dp[x]+mac[x].r-(ll)(mac[x].d+1)*mac[x].g-mac[x].p;
}
pii q[M],stk[M];
inline bool slope(pii a,pii b,pii c){
    return (double)(a.se-b.se)*(c.fi-a.fi)<(double)(b.fi-a.fi)*(a.se-c.se);
}
void solve(int L,int R){
    if(L==R) return;
    int mid=(L+R)>>1;
    solve(L,mid);
    int tot=0,top=0,j=0;
    for(int i=L;i<=mid;i++)
        if(dp[i]>=mac[i].p) q[tot++]=pii(mac[i].g,calc(i));
    sort(q,q+tot);
    for(int i=0;i<tot;i++){
        while(top>1&&!slope(stk[top-1],stk[top],q[i])) top--;
        stk[++top]=q[i];
    }
    for(int i=mid+1;i<=R;i++){
        ll b1,b2,a1,a2,x;
        x=mac[i].d;
        while(j<top){
            a1=stk[j].fi;b1=stk[j].se;
            a2=stk[j+1].fi;b2=stk[j+1].se;
            if(b2+(ll)a2*x<=b1+(ll)a1*x) break;
            j++;
        }
        dp[i]=max(dp[i],stk[j].fi*x+stk[j].se);
    }
    solve(mid+1,R);
}
int main(){
    int n,D,cas=0;
    while(scanf("%d%lld%d",&n,&dp[0],&D)!=EOF,n||dp[0]||D){
        for(int i=1;i<=n;i++)
            mac[i].input();
        sort(mac+1,mac+n+1,cmp);
        n++;
        mac[n].d=D+1;
        mac[n].g=mac[n].p=0;
        for(int i=1;i<=n;i++)
            dp[i]=dp[0];
        solve(0,n);
        printf("Case %d: %lld\n",++cas,dp[n]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值