Codeforces 1107G Vasya and Maximum Profit 线段树最大子段和 + 单调栈

Codeforces 1107G 线段树最大子段和 + 单调栈

G. Vasya and Maximum Profit

Description:

Vasya got really tired of these credits (from problem F) and now wants to earn the money himself! He decided to make a contest to gain a profit.
Vasya has \(n\) problems to choose from. They are numbered from \(1\) to \(n\). The difficulty of the \(i\)-th problem is \(d_i\). Moreover, the problems are given in the increasing order by their difficulties. The difficulties of all tasks are pairwise distinct. In order to add the \(i\)-th problem to the contest you need to pay \(c_i\) burles to its author. For each problem in the contest Vasya gets \(a\) burles.
In order to create a contest he needs to choose a consecutive subsegment of tasks.
So the total earnings for the contest are calculated as follows:
if Vasya takes problem \(i\) to the contest, he needs to pay \(c_i\) to its author; for each problem in the contest Vasya gets \(a\) burles; let \(gap(l, r) = \max\limits_{l \le i < r} (d_{i + 1} - d_i)^2\). If Vasya takes all the tasks with indices from \(l\) to \(r\) to the contest, he also needs to pay \(gap(l, r)\). If \(l = r\) then \(gap(l, r) = 0\). Calculate the maximum profit that Vasya can earn by taking a consecutive segment of tasks.

Input:

The first line contains two integers \(n\) and \(a\) (\(1 \le n \le 3 \cdot 10^5\), \(1 \le a \le 10^9\)) — the number of proposed tasks and the profit for a single problem, respectively.
Each of the next \(n\) lines contains two integers \(d_i\) and \(c_i\) (\(1 \le d_i, c_i \le 10^9, d_i < d_{i+1}\)).

Output

Print one integer — maximum amount of burles Vasya can earn.

Sample Input:

5 10
1 15
5 3
6 11
7 2
11 22

Sample Output:

13

Sample Input:

3 5
1 8
2 19
3 11

Sample Output:

0

题目链接

题解:

你现在有一个题库,里面有\(n\)道题,每道题难度为\(d_i\), 保证\(d_i\)严格单调增,你现在需要从中选择出一个连续子段的题来组成一场比赛,比赛每有一道题你能获得\(a\)的收益,但需要给作者支付\(c_i\)的费用,除此以外,你还需要支付相邻两道题的难度的差值的平方的最大值的费用(可能题目难度跨度过大会引起不满233)

形式话的说,如果你选择了\(l\)\(r\)的题目,最后总收益为\(a \cdot (r-l+1)-\sum_l^rc_i-\max\limits_{l\le i\lt r}(d[i+1]-d[i])^2\), 特别地如果\(l=r\),则第三项为0

首先考虑前两项,显然每道题的收益为\(a-c_i\),如果没有第三项那就是一个最大子段和问题

现在考虑第三项,设\(diff[i] = d[i + 1] - d[i]\),只有\(n-1\)项,考虑这\(n-1\)项分别作为最大值的的区间,就是左边下标最大的比它大的和右边下标最小的比它大的中间的区间即为\(diff[i]\)作为最大值的区间,这个可以用单调栈\(O(n)\)处理出来,然后用线段树跑\(n-1\)次最大子段和就行了,总复杂度为\(O(nlog(n))\)

ps:这里有一个问题,就是对于\(diff[i]\)跑最大子段和时跑出来的区间不一定包含\(i\),但这题不是问题,因为要减去\(diff[i]^2\),不包含的话只会获得更小的值(设该区间\(diff\)最大值为\(diff[j]\),显然\(diff[j] \le diff[i]\),在处理以\(diff[j]\)为最大值的区间时可以获得更大的收益),如果一定要包含那就线段树改成询问\(l\)\(i\)的包含右端点的最大值和\(i+1\)\(r\)的包含左端点的最大值(可以为空)加起来就行了

pps:还有就是整场比赛只有一道题(没有\(diff\)),直接\(O(n)\)处理出来就行了

AC代码:

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 3e5 + 10;

struct tree {
    LL sum, rmx, lmx, mx;
}sgt[N << 2];

int c[N], d[N], diff[N], a, n, l[N], r[N];
long long ans;

void pushup(int rt) {
    sgt[rt].sum = sgt[rt << 1].sum + sgt[rt << 1 | 1].sum;
    sgt[rt].mx = max(sgt[rt << 1].mx, sgt[rt << 1 | 1].mx);
    sgt[rt].lmx = max(sgt[rt << 1].lmx, sgt[rt << 1].sum + sgt[rt << 1 | 1].lmx);
    sgt[rt].rmx = max(sgt[rt << 1 | 1].rmx, sgt[rt << 1 | 1].sum + sgt[rt << 1].rmx);
    sgt[rt].mx = max(sgt[rt].mx, sgt[rt << 1].rmx + sgt[rt << 1 | 1].lmx);
}

void build(int rt, int l, int r) {
    if(l == r) {
        sgt[rt].sum = sgt[rt].lmx = sgt[rt].rmx = sgt[rt].mx = c[l];
        return;
    }
    int mid = l + r >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);
}

tree query(int rt, int l, int r, int L, int R) {
    if(L <= l && r <= R) return sgt[rt];
    int mid = l + r >> 1;
    if(R <= mid) return query(rt << 1, l, mid, L, R);
    if(L > mid)  return query(rt << 1 | 1, mid + 1, r, L, R);
    tree u = query(rt << 1, l, mid, L, R), v = query(rt << 1 | 1, mid + 1, r, L, R), o;
    o.sum = u.sum + v.sum;
    o.mx = max(u.mx, v.mx);
    o.mx = max(o.mx, u.rmx + v.lmx);
    o.lmx = max(u.lmx, u.sum + v.lmx);
    o.rmx = max(v.rmx, v.sum + u.rmx);
    return o;
}

struct node {
    int val, id;
};

int main() {
    scanf("%d %d", &n, &a);
    for(int i = 1; i <= n; ++i) {
        scanf("%d%d", &d[i], &c[i]);
        c[i] = a - c[i];
        ans = max(ans, 1LL * c[i]);
    }
    for(int i = 1; i < n; ++i)
        diff[i] = d[i + 1] - d[i];
    build(1, 1, n);
    stack<node> stk;
    stk.push({(int)1e9, 0});
    for(int i = 1; i < n; ++i) {
        while(stk.top().val <= diff[i])
            stk.pop();
        l[i] = stk.top().id + 1;
        stk.push({diff[i], i});
    }
    while(!stk.empty()) stk.pop();
    stk.push({(int)1e9, n});
    for(int i = n - 1; i; --i) {
        while(stk.top().val <= diff[i])
            stk.pop();
        r[i] = stk.top().id;
        stk.push({diff[i], i});
    }
    for(int i = 1; i < n; ++i) {
        ans = max(ans, query(1, 1, n, l[i], r[i]).mx - 1LL * diff[i] * diff[i]);
    }
    printf("%lld\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/tusikalanse/p/10347821.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值