【CF1107G】Vasya and Maximum Profit(单调栈/单调栈+线段树最大子段和)

传送门

  • 题目:
    在这里插入图片描述
  • 思路:
    • 式子:
      e a r n [ i ] = a − c [ i ] earn[i]=a-c[i] earn[i]=ac[i] s u m [ i ] = ∑ j = 1 i e a r n [ j ] , ( 1 ≤ i ≤ n ) sum[i]=\sum_{j=1}^{i}earn[j],(1≤i≤n) sum[i]=j=1iearn[j],(1in) a n s = m a x { s u m [ r ] − s u m [ l − 1 ] + g a p ( l , r ) } ans=max\{sum[r]-sum[l-1]+gap(l,r)\} ans=max{sum[r]sum[l1]+gap(l,r)}
    • 纯单调栈: O ( n ) O(n) O(n)
      从后往前枚举左端点: a n s = m a x { s u m [ r ] + g a p ( l , r ) − s u m [ l − 1 ] } ans=max\{sum[r]+gap(l,r)-sum[l-1]\} ans=max{sum[r]+gap(l,r)sum[l1]}左端点固定时,式子中的最后一项 s u m [ l − 1 ] sum[l-1] sum[l1]是一定的,每当左端点向左移动一位时, g ( l , r ) g(l,r) g(l,r)可能会改变。
      假定左端点向左移动一位时新增的差值为 s d , s d = d [ l + 1 ] − d [ l ] sd,sd=d[l+1]-d[l] sd,sd=d[l+1]d[l],考虑以 s d sd sd g ( l , r ) g(l,r) g(l,r)的右端点 r r r,且在这些右端点中记录最大的 m a x [ r ] max[r] max[r],记为 v a l val val,那么 v a l − s d ∗ s d − e a r n [ l ] val-sd*sd-earn[l] valsdsdearn[l]就可能成为最大值,记为A。同时也要记录 [ l + 1 , r ] [l+1,r] [l+1,r]内最大合法的的 s u m [ r ] + g a p ( l + 1 , r ) sum[r]+gap(l+1,r) sum[r]+gap(l+1,r),记为B,并取 m a x ( A , B ) − e a r n [ l − 1 ] max(A,B)-earn[l-1] max(A,B)earn[l1]更新答案。
      因为是新值先处理的思路,所以用栈来做。注意 l = r l=r l=r的情况,状态要从合法的情况转移过来。
    • 单调栈+线段树最大子段和: O ( n l o g n ) O(nlogn) O(nlogn)
      观察式子 a n s = m a x { s u m [ r ] − s u m [ l − 1 ] + g a p ( l , r ) } ans=max\{sum[r]-sum[l-1]+gap(l,r)\} ans=max{sum[r]sum[l1]+gap(l,r)},如果没有最后一项,前面可以通过线段树求 [ 1 , n ] [1,n] [1,n]的最大子段和得到。有最后一项的话,枚举差值 s d sd sd,通过单调栈预处理出以当前位置的 s d sd sd g a p ( l , r ) gap(l,r) gap(l,r)的区间 l , r l,r l,r,在 l , r l,r l,r区间内查询最大子段和,不断更新答案,特殊处理下选择 l = r l=r l=r这种情况。
      大佬详解
  • ac代码:
    纯单调栈做法:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5+10;
int n;
ll a, c;
ll d[maxn], earn[maxn];
struct node{
    ll  sub, val, mx_all;
}s[maxn];
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    scanf("%d %lld", &n, &a);
    for(int i = 1; i <= n; i++) scanf("%lld %lld", &d[i], &c), earn[i] = earn[i-1]+(a-c);
    int top = 0;
    ll ans = max(0ll, a-c);
    s[++top] = {0, earn[n], earn[n]};
    s[0].mx_all = LLONG_MIN;
    for(int i = n-1; i >= 1; i--)
    {
        ll sd = d[i+1]-d[i], mx = LLONG_MIN;
        while(top>0 && s[top].sub<=sd) mx = max(mx, s[top--].val);
        top++; s[top] = {sd, mx, max(s[top-1].mx_all, mx-sd*sd)};
        top++; s[top] = {0, earn[i], max(s[top-1].mx_all, earn[i])};
        ans = max(ans, s[top].mx_all-earn[i-1]);
    }
    printf("%lld\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值