SP1557 GSS2 - Can you answer these queries II【线段树】

题意

给出 n n n 个数, q q q 次询问,求询问区间的最大子段和,相同的数只算一次。
题目链接

思路

询问区间最大子段和是线段树的经典操作,通过维护每个区间包括左端点的最大子段和和包括右端点的最大子段和可以轻松的实现。
但是此题有一个限制,相同的数只能算一次,于是,无法使用原先简单的维护方法。
m x [ i , j ] mx[i,j] mx[i,j]表示以 i i i为区间左端点,区间右端点从 i i i j j j时所有区间的和(不算重复元素的和)的最大值。考虑区间 [ l , r ] [l, r] [l,r]中的所有子段,最大子段和可以转化为 m a x i { m x [ i , r ] } ( l ≤ i ≤ r ) max_i\{mx[i, r]\}(l \le i \le r) maxi{mx[i,r]}(lir)。考虑 m x [ i , r ] mx[i, r] mx[i,r]如何计算,其实相当于是 [ l , r ] [l,r] [l,r]区间中本质不同元素累加期间的最大值(也称为历史最大值),于是问题就转化为了维护 [ l , r ] [l,r] [l,r]区间历史最大值的最大值。维护的方法参考这一题洛谷P4314。。
通常,相同的数只算一次的问题可以考虑采取离线操作。我们将所有询问离线并按照右端点升序排序。
考虑我们处理到了第 i i i个位置,假设 a [ i ] a[i] a[i]在之前出现的位置为 p r e [ i ] pre[i] pre[i],那么将区间 [ p r e [ i ] + 1 , i ] [pre[i] + 1, i] [pre[i]+1,i]加上 a [ i ] a[i] a[i](区间加法)即可。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <vector>
#include <map>
#include <queue>
#include <cmath>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 100005;

int a[N], pre[N];
int pos[N * 2];
LL ans[N];
struct TreeNode
{
    int l, r;
    LL mx, mx_his;
    LL addlz, add_his;
}t[N << 2];
struct Query
{
    int l, r, id;
    bool operator < (const Query& b) const 
    {
        return r < b.r;
    }
}q[N];

void push_up(int i)
{
    t[i].mx = max(t[i << 1].mx, t[i << 1 | 1].mx);
    t[i].mx_his = max(t[i << 1].mx_his, t[i << 1 | 1].mx_his);
}

void Add(int i, LL add, LL his_add)
{
    t[i].add_his = max(t[i].add_his, t[i].addlz + his_add);
    t[i].mx_his = max(t[i].mx_his, t[i].mx + his_add);
    t[i].mx += add;
    t[i].addlz += add;
}

void push_down(int i)
{
    Add(i << 1, t[i].addlz, t[i].add_his);
    Add(i << 1 | 1, t[i].addlz, t[i].add_his);
    t[i].add_his = t[i].addlz = 0;
}

void build(int i, int l, int r)
{
    t[i].l = l, t[i].r = r;
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(i << 1, l, mid);
    build(i << 1 | 1, mid + 1, r);
    push_up(i);
}

void update(int i, int l, int r, LL k)
{
    if (l <= t[i].l && t[i].r <= r)
    {
        Add(i, k, k);
        return;
    }
    push_down(i);
    int mid = (t[i].l + t[i].r) >> 1;
    if (l <= mid) update(i << 1, l, r, k);
    if (r > mid) update(i << 1 | 1, l, r, k);
    push_up(i);
}

LL query(int i, int l, int r)
{
    if (l <= t[i].l && t[i].r <= r) return t[i].mx_his;
    push_down(i);
    int mid = (t[i].l + t[i].r) >> 1;
    LL mx = -1e18;
    if (l <= mid) mx = max(mx, query(i << 1, l, r));
    if (r > mid) mx = max(mx, query(i << 1 | 1, l, r));
    return mx;
}

int main()
{
    #ifdef ZYCMH
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    #endif
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
        scanf("%d", &a[i]);
    build(1, 1, n);
    
    for (int i = 1; i <= n; i ++ )
    {
        pre[i] = pos[a[i] + 100000];
        pos[a[i] + 100000] = i;
    }

    int m;
    scanf("%d", &m);
    for (int i = 1; i <= m; i ++ )
    {
        scanf("%d%d", &q[i].l, &q[i].r);
        q[i].id = i;
    }

    sort(q + 1, q + 1 + m);

    int cur = 1;
    for (int i = 1; i <= n; i ++ )
    {
        update(1, pre[i] + 1, i, a[i]);
        while(cur <= m && q[cur].r <= i)
            ans[q[cur].id] = query(1, q[cur].l, q[cur].r), cur ++;
    }

    for (int i = 1; i <= m; i ++ )
        printf("%lld\n", ans[i]);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值