SPOJ GSS系列解题报告

众所周知,\(GSS\)是用来练习线段树的一个非常好的系列。

依稀记得\(8\)月初还在\(qbxt\)时,某钟姓神仙说过 :“大家可以把\(GSS\)系列写一下,大概够大家写一个月了。”虽然我很不想承认,但他确实说中了\(……\)虽然中间夹有做各种杂题和一些模拟赛\(……\)

题解

GSS 1

\(GSS\)系列最简单的一道题,也算是后面题目的一个基础。

给定一个序列,维护区间最大子段和。

我们维护这样几个东西\(:lsum, rsum, maxsum, maxval.\)

分别表示区间最大前缀和,区间最大后缀和,区间最大子段和,区间最大值,其中最大前缀和与最大后缀和用于辅助求出最大子段和。

我们考虑一下如何合并左右两个区间:

  • 对于当前区间的\(lsum\),我们取左区间的\(lsum\)和左区间的\(sum+\)右区间的\(lsum\)中的最大值来更新。
  • 对于当前区间的\(rsum\),我们采取与维护\(lsum\)相似的方式维护,取右区间的\(rsum\)和左区间的\(lsum+\)右区间的\(sum\)中的最大值来更新。
  • 对于当前区间的\(maxsum\),即最大子段和,我们取左区间的\(maxsum\)和右区间的\(maxsum\)和左区间的\(rsum+\)右区间的\(lsum\)中的最大值来更新当前区间的\(maxsum\)。
  • 对于当前区间的\(maxval\),正常维护即可。之所以要维护\(maxval\),是因为当我们发现要询问的区间的最大值小于0时,直接输出当前区间的\(maxval\)即可。

另外,关于最大子段和,版本不同对于最大子段和是否可以为空要求不同,所以代码细节可能会比较多,对于像我一样粗心的人可能会不太友好\(……\)

code:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int maxn = 5e4 + 5;
int w, ch, n, m, a[maxn];

template<class T>
T read(T &x)
{
    x = 0, w = 1, ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - 48; ch = getchar();}
    return x *= w;
}

struct node {
    int l, r, maxsum, sum, maxval;
    int fmax, bmax;
}z[maxn << 2];

node operator + (const node &a, const node &b)
{
    node ans;
    ans.l = a.l;
    ans.r = b.r;
    ans.sum = a.sum + b.sum;
    ans.maxval = max(a.maxval, b.maxval);
    ans.maxsum = max(a.maxsum, max(b.maxsum, a.bmax + b.fmax));
    ans.fmax = max(a.fmax, a.sum + b.fmax);
    ans.bmax = max(b.bmax, b.sum + a.bmax);
    return ans;
}

void pushup(int rt)
{
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

void build(int rt, int l, int r)
{
    if (l == r) {
        z[rt].l = l, z[rt].r = r;
        z[rt].sum = z[rt].maxval = a[l];
        if (z[rt].sum < 0) {
            z[rt].maxsum = z[rt].fmax = z[rt].bmax = 0;
        }
        else {
            z[rt].maxsum = z[rt].fmax = z[rt].bmax = a[l];
        }
        return ;
    }
    int mid = (l + r) >> 1;
    build (rt << 1, l, mid);
    build (rt << 1 | 1, mid + 1, r);
    pushup(rt);
}

node query(int rt, int l, int r)
{
    if (z[rt].l == l && z[rt].r == r)
        return z[rt];
    int mid = (z[rt].l + z[rt].r) >> 1;
    if (r <= mid)
        return query(rt << 1, l, r);
    if (l > mid)
        return query(rt << 1 | 1, l, r);
    return query(rt << 1, l, mid) + query(rt << 1 | 1, mid + 1, r);
}

int main()
{
    read(n);
    for (int i = 1; i <= n; i++) read(a[i]);
    build(1, 1, n);
    read(m);
    int L, R;
    while (m--) {
        read(L), read(R);
        node ans = query(1, L, R);
        if (ans.maxval < 0) cout << ans.maxval << '\n';
        else cout << ans.maxsum << '\n';
    }
    return 0;
}


GSS 2

讲道理,窝\(jio\)得这是\(GSS\)系列中最难的一道题,所以窝也不知道为什么这道题会放在第二位。

看题面,我们会发现这道题仅仅比\(GSS 1\)多了一个判重的操作,然后\(……\)他的难度就上天了!!

遇到这种需要去重的问题,我们采取一个比较套路的方法:离线。

没错!!这是一道离线神题!!

考虑到我们需要去重,\(GSS1\)中用到的记录区间前缀,后缀的方法就不再适用了,所以我们需要一棵很强的线段树来维护一些很强的东西!!

先说思路和做法,对于本题我们维护的线段树,线段树的叶子结点\(j\)表示\(j\)到当前节点\(i\)这个区间的最大子段和,即\(:\)

\(max\{A[l]+A[l+1]+……+A[r]\}\) 其中 \(j \leq l \leq r \leq i\)

我们维护这样几个东西\(:\)当前区间最大子段和\(maxsum\),区间历史最大子段和\(hismaxsum\),懒标记\(tag\),历史最大懒标记\(histag\)

我们将所有的询问按照右端点排序,每次将一个数插入线段树中,仔细思考一下,我们更新之前每一个节点,其实就是在进行一个区间加的过程,但我们需要去重,一个简便的方法\(:\)记录一个\(pre[a[j]]\)来记录\(a[j]\)前一次出现的位置,这样我们每次修改\(\{pre[a[j]]+1, i\}\)即可避免\(a[j]\)对之前造成影响,当我们当前节点与询问的右端点相同时,即可更新并记录答案,更新答案时我们发现,当前的\(maxsum\)不一定是最大子段和,而\(hismaxsum\)才是,原因是显然的。

代码细节\(:\)

  • 在进行\(pushdonw\)时,对于子区间的\(hsimaxsum\)和\(histag\)直接下放当前区间的\(histag\),原因和更新答案的原理是相似的。
  • 在向线段树中插入时,要注意更新的顺序,先更新\(maxsum\)和\(tag\),再更新\(hismaxsum\)和\(histag\),这样就保证了区间的连续性。
  • 剩下就是一些细节要注意,例如记录\(pre\)数组时,为防止出现负下标,把每个数整体向后移100000位\(……\)

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long

namespace LXC
{
    template<class T>
    inline T read(T &x)
    {
        x = 0; int w = 1, ch = getchar();
        while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
        while (ch >= '0' && ch <= '9') {x = x * 10 + ch - 48; ch = getchar();}
        return x *= w;
    }

    template<class T>
    inline T max(const T a, const T b)
    {
        return a > b ? a : b;
    }
}
using namespace LXC;
using std::sort;

const int maxn = 2e5 + 5;
int n, m, a[maxn], pre[maxn << 1];
int x[maxn], y[maxn], idx[maxn];

bool cmp(int a, int b)
{
    return y[a] < y[b];
}

struct Node {
    int l, r;
    ll maxsum, maxsum_old; // 当前贡献最大值   从开始到现在历史贡献最大值
    ll tag, tag_old; // 懒标记 从上次pushdown到现在最大懒标记
}z[maxn << 2], ans[maxn];

Node operator + (const Node &a, const Node &b)
{
    Node ans;
    ans.l = a.l;
    ans.r = b.r;
    ans.maxsum = max(a.maxsum, b.maxsum);
    ans.maxsum_old = max(a.maxsum_old, b.maxsum_old);
    ans.tag = ans.tag_old = 0;
    return ans;
}

void pushdown(int rt)
{
    int ls = rt << 1, rs = rt << 1 | 1;
    ll v_old = z[rt].tag_old, v = z[rt].tag;
    z[ls].maxsum_old = max(z[ls].maxsum_old, z[ls].maxsum + v_old);
    z[ls].tag_old = max(z[ls].tag_old, z[ls].tag + v_old);
    z[ls].maxsum += v;
    z[ls].tag += v;
    z[rs].maxsum_old = max(z[rs].maxsum_old, z[rs].maxsum + v_old);
    z[rs].tag_old = max(z[rs].tag_old, z[rs].tag + v_old);
    z[rs].maxsum += v;
    z[rs].tag += v;
    z[rt].tag = z[rt].tag_old = 0;
}

void modify(int rt, int L, int R, int l, int r, ll w)
{
    if (L <= l && r <= R) {
        z[rt].maxsum += w, z[rt].maxsum_old = max(z[rt].maxsum_old, z[rt].maxsum);
        z[rt].tag += w, z[rt].tag_old = max(z[rt].tag_old, z[rt].tag);
        return ;
    }
    int mid = (l + r) >> 1;
    pushdown(rt);
    if (L <= mid)
        modify(rt << 1, L, R, l, mid, w);
    if (R > mid)
        modify(rt << 1 | 1, L, R, mid + 1, r, w);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

Node query(int rt, int L, int R, int l, int r)
{
    if (L <= l && r <= R) return z[rt];
    int mid = (l + r) >> 1;
    pushdown(rt);
    if (R <= mid)
        return query(rt << 1, L, R, l, mid);
    if (L > mid)
        return query(rt << 1 | 1, L, R, mid + 1, r);
    return query(rt << 1, L, R, l, mid) + query(rt << 1 | 1, L, R, mid + 1, r);
}

int main()
{
    read(n);
    for (int i = 1; i <= n; i++) read(a[i]);
    read(m);
    for (int i = 1; i <= m; i++) {
        read(x[i]), read(y[i]);
        idx[i] = i;
    }
    sort(idx + 1, idx + m + 1, cmp);
    int j = 1;
    for (int i = 1; i <= n; i++) {
        modify(1, pre[a[i] + 100000] + 1, i, 1, n, a[i]);
        pre[a[i] + 100000] = i;
        while (y[idx[j]] == i && j <= m) ans[idx[j]] = query(1, x[idx[j]], y[idx[j]], 1, n), j++;
    }
    for (int i = 1; i <= m; i++) printf("%lld\n", ans[i].maxsum_old);
    return 0;
}


GSS 3

没什么可说的,依旧是查询最大子段和,只比\(GSS1\)多了一个单点修改。

当然,单点修改对于我们的维护是没有任何影响的,但如果是区间修改\(……\)标记的下放可能会比较恶心,有时间可以考虑一下。

code:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int maxn = 5e4 + 5;
int w, ch, n, m, a[maxn];

template<class T>
T read(T &x)
{
    x = 0, w = 1, ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - 48; ch = getchar();}
    return x *= w;
}

struct node {
    int l, r, maxsum, sum, maxval;
    int fmax, bmax;
}z[maxn << 2];

node operator + (const node &a, const node &b)
{
    node ans;
    ans.l = a.l;
    ans.r = b.r;
    ans.sum = a.sum + b.sum;
    ans.maxval = max(a.maxval, b.maxval);
    ans.maxsum = max(a.maxsum, max(b.maxsum, a.bmax + b.fmax));
    ans.fmax = max(a.fmax, a.sum + b.fmax);
    ans.bmax = max(b.bmax, b.sum + a.bmax);
    return ans;
}

void pushup(int rt)
{
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

void build(int rt, int l, int r)
{
    if (l == r) {
        z[rt].l = l, z[rt].r = r;
        z[rt].sum = z[rt].maxval = a[l];
        if (z[rt].sum < 0) {
            z[rt].maxsum = z[rt].fmax = z[rt].bmax = 0;
        }
        else {
            z[rt].maxsum = z[rt].fmax = z[rt].bmax = a[l];
        }
        return ;
    }
    int mid = (l + r) >> 1;
    build (rt << 1, l, mid);
    build (rt << 1 | 1, mid + 1, r);
    pushup(rt);
}

void modify(int rt, int x, int y)
{
    if (z[rt].l == z[rt].r) {
        if (y < 0){
            z[rt].maxsum = z[rt].fmax = z[rt].bmax = 0;
            z[rt].sum = z[rt].maxval = y;
        }
        else
            z[rt].sum = z[rt].maxval = z[rt].maxsum = z[rt].fmax = z[rt].bmax = y;
        return ;
    }
    int mid = (z[rt].l + z[rt].r) >> 1;
    if (x <= mid)
        modify(rt << 1, x, y);
    else
        modify(rt << 1 | 1, x, y);
    pushup(rt);
}

node query(int rt, int l, int r)
{
    if (z[rt].l == l && z[rt].r == r)
        return z[rt];
    int mid = (z[rt].l + z[rt].r) >> 1;
    if (r <= mid)
        return query(rt << 1, l, r);
    if (l > mid)
        return query(rt << 1 | 1, l, r);
    return query(rt << 1, l, mid) + query(rt << 1 | 1, mid + 1, r);
}

int main()
{
    read(n);
    for (int i = 1; i <= n; i++) read(a[i]);
    build(1, 1, n);
    read(m);
    int opt, L, R;
    while (m--) {
        read(opt), read(L), read(R);
        if (opt == 0)
            modify(1, L, R);
        else {
            node ans = query(1, L, R);
            if (ans.maxval < 0) cout << ans.maxval << '\n';
            else cout << ans.maxsum << '\n';
        }
    }
    return 0;
}


GSS 4

两个操作\(:\)区间开方,区间求和。

区间求和是很常规的操作,我们暂不考虑。

来看这个比较难搞的区间开方操作,很容易就可以想到对一个区间的\(sum\)进行开方,但我们仔细一想,开方和和开方是肯定不一样的,所以我们\(……\)暴力来搞。

仔细算一下,一个\(10^{18}\)级别的数,只要对其开方六次,他就会变成\(1\),我们就可以不再对它进行开方,这样就省去了大量的冗余操作,时间复杂度上限\(O(6 * 10^5)\),这是可以接受的。

需要一提的是,这道题目是多组测试数据。不过它好像没什么数组需要清空……

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define LL long long

using namespace std;

const int maxn = 1000005;

LL L[maxn<<2], R[maxn<<2], a[maxn<<2], tag[maxn<<2], sum[maxn<<2], ans;
int n, m;

void pushup(int rt)
{
    sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    tag[rt] = tag[rt<<1] && tag[rt<<1|1];
}

void build(int rt,int l,int r)
{
    L[rt] = l, R[rt] = r;
    if (l == r) {
        sum[rt] = a[l];
        if (a[l] == 1) tag[rt] = 1;
        else tag[rt] = 0;
        return ;
    }
    int mid = (l+r) >> 1;
    build (rt << 1, l, mid);
    build (rt << 1 | 1, mid + 1, r);
    pushup(rt);
}

void modify(int rt, int l, int r)
{
    if (tag[rt]) return ;
    if (L[rt] == R[rt]) {
        sum[rt] = (LL)(sqrt(sum[rt]));
        if (sum[rt] <= 1)
            tag[rt] = 1;
        return ;
    }
    int mid = (L[rt] + R[rt]) >> 1;
    if (r <= mid)
        modify(rt << 1, l, r);
    if (l > mid)
        modify(rt << 1 | 1, l, r);
    if (l <= mid && r > mid) {
        modify(rt << 1, l, mid);
        modify(rt << 1 | 1, mid + 1, r);
    }
    pushup(rt);
}

LL query(int rt, int l, int r)
{
    if (L[rt] == l && R[rt] == r) return sum[rt];
    int mid = (L[rt] + R[rt]) >> 1;;
    if (r <= mid)
        return query(rt << 1, l, r);
    if (l > mid)
        return query(rt << 1 | 1, l, r);
    return query(rt << 1, l, mid) + query(rt << 1 | 1, mid + 1, r);
}
int main()
{
    int Case = 0;
    while (scanf("%d", &n) != EOF) {
        printf("Case #%d:\n",++Case);
        for (int i = 1; i <= n; i++) cin >> a[i];
        build (1, 1, n);
        scanf("%d", &m);
        int opt, x, y, i = 1;
        while (m--) {
            scanf("%d", &opt);
            if (opt == 0) {
                scanf("%d%d", &x, &y);
                if (x > y) swap(x, y);
                modify(1, x, y);
            }
            else {
                scanf("%d%d", &x, &y);
                if(x > y) swap(x, y);
                printf("%lld\n", query(1, x, y));
            }
        }
        puts("");
    }
    return 0;
}


GSS 5

依旧是求最大子段和的问题,不过它强制左右端点在两个区间\(\{x_1, y_1\} \{x_2, y_2\}\)中。

我们考虑分类讨论:

  • 当\(y_1 < x_2\)时,即两个区间不重叠,那么最大子段和很显然就是\(\{x_1,y_1\}\)的最大后缀和\(+\{y_1,x_2\}\)的区间和\(+\{x_2,y_2\}\)的最大前缀和。

图片较简陋,凑合着看……

  • 当\(x_2 < y_1\)时,即两个区间有重叠部分,如同上图描述的情况,这时,我们需要考虑四种情况,区间\(\{y_1,x_2\}\)的区间和\((\)上图蓝色线段\()\),区间\(\{x_1,y_1\}\)的最大后缀和\(+\{y_1,x_2\}\)的区间和\((\)上图黄色线段\()\),区间\(\{y_1,x_2\}\)的区间和\(+\{x_2,y_2\}\)的最大前缀和\((\)上图粉色线段\()\),区间\(\{x_1,y_1\}\)的最大后缀和\(+\{y_1,x_2\}\)的区间和\(+\{x_2,y_2\}\)的最大前缀和\((\)上图绿色线段\()\)。

此题还有两个需要注意的地方\(:\)

  • 当查询到\(l > r\)时要返回空节点,否则会像我一样\(RE\)到死\(……\)
  • 此题的最大子段和没有要求不能为空,所以我们在每次更新时都需要和\(0\)进行比较。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include<bits/stdc++.h>

namespace LXC
{
    template<class T>
    inline T read(T &x)
    {
        x = 0; int w = 1, ch = getchar();
        while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
        while (ch >= '0' && ch <= '9') {x = x * 10 + ch - 48; ch = getchar();}
        return x *= w;
    }

    template<class T>
    inline T max(const T a, const T b)
    {
        return a > b ? a : b;
    }
}
using namespace LXC;

const int maxn = 1e4 + 5;
int T, n, m, a[maxn];

struct Node {
    int l, r, sum, lsum, rsum, maxsum;
}z[maxn << 2], nmsl;

Node operator + (const Node &a, const Node &b)
{
    Node ans;
    ans.l = a.l;
    ans.r = b.r;
    ans.lsum = max(a.lsum, a.sum + b.lsum);
    ans.rsum = max(b.rsum, b.sum + a.rsum);
    ans.maxsum = max(max(a.maxsum, b.maxsum), a.rsum + b.lsum);
    ans.sum = a.sum + b.sum;
    return ans;
}

void build(int rt, int l, int r)
{
    z[rt].l = l, z[rt].r = r;
    if (l == r) {
        z[rt].sum = z[rt].lsum = z[rt].rsum = z[rt].maxsum = a[l];
        return ;
    }
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

Node query(int rt, int l, int r)
{
    if (l > r) return nmsl;
    if (l == z[rt].l && z[rt].r == r) return z[rt];
    int mid = (z[rt].l + z[rt].r) >> 1;
    if (r <= mid)
        return query(rt << 1, l, r);
    if (l > mid)
        return query(rt << 1 | 1, l, r);
    return query(rt << 1, l, mid) + query(rt << 1 | 1, mid + 1, r);
}

int main()
{
    read(T);
    while (T--) {
        read(n);
        for (int i = 1; i <= n; i++) read(a[i]);
        build(1, 1, n);
        read(m);
        int x1, y1, x2, y2;
        for (int i = 1; i <= m; i++) {
            read(x1), read(y1), read(x2), read(y2);
            if (y1 < x2) {
                printf("%d\n", max(query(1, x1, y1 - 1).rsum, 0) + query(1, y1, x2).sum + max(query(1, x2 + 1, y2).lsum, 0));
            }
            else {
                int ans = query(1, x2, y1).maxsum;
                ans = max(ans, query(1, x1, x2 - 1).rsum + query(1, x2, y1).lsum);
                ans = max(ans, query(1, x2, y1).rsum + query(1, y1 + 1, y2).lsum);
                ans = max(ans, max(query(1, x1, x2 - 1).rsum, 0) + query(1, x2, y1).sum + max(0, query(1, y1 + 1, y2).lsum));
                printf("%d\n", ans);
            }
        }
    }
    return 0;
}


GSS 6

题目要求:插入元素,删除元素,单点修改,查询最大子段和。

很明显看到插入操作和删除操作,我们就知道这题要用平衡树,本人使用\(fhq~Treap\)来实现这些操作。

一些需要注意的点\(:\)

  • 在进行\(split\)时,我们习惯上采用按照权值来分,但这道题在哪个位置就把另设的一个权值标上位置,所以我们直接按照点的\(size\)来分。
  • 由于是平衡树,我们需要考虑左右子树是否为空的情况,所以我们采取常用的分类讨论的方式,唯一一点就是这样写下来你的\(update\)函数可能会巨长\(……\)
  • 本题我们用平衡树,因此在维护一个节点时,会分成左子树,自己,右子树三个部分,和线段树略有不同。
  • 这个题需要的空间很大,推荐直接开\(4\)倍。
  • 十年\(OI\)一场空,不开\(long~long\)见祖宗!!

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <ctime>
#define LL long long

namespace LXC
{
    template<class T>
    inline T read(T &x)
    {
        x = 0; int w = 1, ch = getchar();
        while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
        while (ch >= '0' && ch <= '9') {x = x * 10 + ch - 48; ch = getchar();}
        return x *= w;
    }

    template<class T>
    inline T max(const T a, const T b)
    {
        return a > b ? a : b;
    }
}

using namespace LXC;

const int maxn = 4e5 + 5;
int n, m;
int l, r, _z, temp, opt;

struct Node {
    int ls, rs, size, key;
    LL val, sum, lsum, rsum, maxsum;
}z[maxn];
int root, cnt;

inline void update(int rt)
{
    z[rt].sum = z[rt].val, z[rt].size = 1;
    if (z[rt].ls)
        z[rt].sum += z[z[rt].ls].sum, z[rt].size += z[z[rt].ls].size;
    if (z[rt].rs)
        z[rt].sum += z[z[rt].rs].sum, z[rt].size += z[z[rt].rs].size;
    if (z[rt].ls && z[rt].rs) {
        z[rt].lsum = max(z[z[rt].ls].lsum, z[z[rt].ls].sum + z[rt].val + z[z[rt].rs].lsum);
        z[rt].rsum = max(z[z[rt].rs].rsum, z[z[rt].rs].sum + z[rt].val + z[z[rt].ls].rsum);
        z[rt].maxsum = max(z[z[rt].ls].maxsum, max(z[z[rt].rs].maxsum, z[z[rt].ls].rsum + z[z[rt].rs].lsum + z[rt].val));
    }
    else if (z[rt].ls) {
        z[rt].lsum = max(max(z[z[rt].ls].lsum, z[z[rt].ls].sum + z[rt].val), 0ll);
        z[rt].rsum = max(z[z[rt].ls].rsum + z[rt].val, 0ll);
        z[rt].maxsum = max(z[z[rt].ls].maxsum, z[z[rt].ls].rsum + z[rt].val);
    }
    else if (z[rt].rs) {
        z[rt].lsum = max(z[z[rt].rs].lsum + z[rt].val, 0ll);
        z[rt].rsum = max(max(z[z[rt].rs].rsum, z[z[rt].rs].sum + z[rt].val), 0ll);
        z[rt].maxsum = max(z[z[rt].rs].maxsum, z[z[rt].rs].lsum + z[rt].val);
    }
    else {
        z[rt].lsum = z[rt].rsum = max(z[rt].val, 0ll);
        z[rt].maxsum = z[rt].val;
    }
}

inline int insert(int x)
{
    ++cnt;
    z[cnt].size = 1;
    z[cnt].key = rand();
    z[cnt].val = z[cnt].sum = z[cnt].maxsum = x;
    z[cnt].lsum = z[cnt].rsum = max(x, 0);
    return cnt;
}

inline int merge(int x, int y)
{
    if (!x || !y) return x + y;
    if (z[x].key < z[y].key) {
        z[x].rs = merge(z[x].rs, y);
        update(x);
        return x;
    }
    else {
        z[y].ls = merge(x, z[y].ls);
        update(y);
        return y;
    }
}

inline void split(int rt, int x, int &l, int &r)
{
    if (!rt) {l = r = 0; return ;}
    if (x <= z[z[rt].ls].size) {
        r = rt, split(z[rt].ls, x, l, z[rt].ls);
    }
    else {
        l = rt, split(z[rt].rs, x - z[z[rt].ls].size - 1, z[rt].rs, r);
    }
    update(rt);
}

int main()
{
    char ch;
    srand((unsigned)time(NULL));
    read(n);
    for (int i = 1; i <= n; i++) {
        read(temp);
        split(root, i, l, r);
        root = merge(merge(l, insert(temp)), r);
    }
    read(m);
    for (int i = 1; i <= m; i++) {
        std::cin >> ch;
        if (ch == 'I') {
            read(opt), read(temp);
            split(root, opt - 1, l, r);
            root = merge(merge(l, insert(temp)), r);
        }
        else if (ch == 'D') {
            read(opt);
            split(root, opt, l, _z);
            split(l, opt - 1, l, r);
            r = merge(z[r].ls, z[r].rs);
            root = merge(merge(l, r), _z);
        }
        else if (ch == 'R') {
            read(opt), read(temp);
            split(root, opt, l, _z);
            split(l, opt - 1, l, r);
            z[r].val = temp;
            z[r].sum = z[r].maxsum = temp;
            z[r].lsum = z[r].rsum = max(temp, 0);
            root = merge(merge(l, r), _z);
        }
        else {
            read(opt), read(temp);
            split(root, temp, l, _z);
            split(l, opt - 1, l, r);
            printf("%lld\n", z[r].maxsum);
            root = merge(merge(l, r), _z);
        }
    }
    return 0;
}


GSS 7

\(GSS\)系列唯一一道树上的题目,虽然仅仅是将操作挪到了树上,但代码的难度确实是倍增\(……\)

考虑到我们的操作是在树上,所以我们首先用树链剖分将它映射到序列上,然后对序列进行操作。

本人觉得这道题的难点在于询问操作上,其他的操作都很常规。

关于询问操作\(:\)

  • 考虑到两个点都在向他们的\(LCA\)跳,我们分别记录两个答案\(L,R\),最后返回答案时,再用重载后的加法将\(L\)和\(R\)合并起来。
  • 再我们进行最后一步合并前,我们需要交换一下\(L\)的最大前缀和和最大后缀和。不太明白? 看图\(:\)

例如,我们要求\(9\)号节点到\(14\)号节点这条链上的最大子段和,当这两个点跳到他们的\(LCA\)即\(1\)号节点时进行合并操作,但我们发现,这时\(L\)的最大前缀和和\(R\)的最大后缀和连在了一起,如果这时合并,很明显是错的,所以我们需要将\(L\)的最大前缀和和最大后缀和交换一下。

然后,注意一下代码细节和常数问题就好了。

code:

#include <iostream>
#include <cstdio>
#include <cstring>

namespace LXC
{
    template<class T>
    inline T read(T &x)
    {
        x = 0;
        int w = 1, ch = getchar();
        while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
        while (ch >= '0' && ch <= '9') {x = x * 10 + ch - 48; ch = getchar();}
        return x *= w;
    }

    template<class T>
    inline T max(const T a, const T b)
    {
        return a > b ? a :b;
    }
}

using namespace LXC;

const int maxn = 1e5 + 5;
int n, m, val[maxn], head[maxn], tot;
int dep[maxn], size[maxn], fa[maxn], son[maxn], top[maxn], idx[maxn], a[maxn], num, cnt;

struct Node {
    int sum, lsum, rsum, maxsum, tag;
    bool cov;
    Node(){sum = lsum = rsum = maxsum = 0;}
}z[maxn << 2];

struct Edge {
    int to, nxt;
}edge[maxn << 1];

void add(int from, int to)
{
    edge[++tot].to = to;
    edge[tot].nxt = head[from];
    head[from] = tot;
}

void dfs1(int u, int f, int deep)
{
    dep[u] = deep;
    fa[u] = f;
    size[u] = 1;
    for (int i = head[u]; i; i = edge[i].nxt) {
        int v = edge[i].to;
        if (v == f) continue;
        dfs1(v, u, deep + 1);
        size[u] += size[v];
        if (size[v] > size[son[u]]) son[u] = v;
    }
}

void dfs2(int u, int topf)
{
    top[u] = topf;
    idx[u] = ++num;
    a[num] = val[u];
    if (!son[u]) return ;
    dfs2(son[u], topf);
    for (int i = head[u]; i; i = edge[i].nxt) {
        int v = edge[i].to;
        if (v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

Node operator + (const Node &a, const Node &b)
{
    Node ans;
    ans.sum = a.sum + b.sum;
    ans.lsum = max(a.lsum, a.sum + b.lsum);
    ans.rsum = max(b.rsum, a.rsum + b.sum);
    ans.maxsum = max(max(a.maxsum, b.maxsum), a.rsum + b.lsum);
    ans.tag = ans.cov = 0;
    return ans;
}

void build(int rt, int l, int r)
{
    if (l == r) {
        z[rt].sum = a[l];
        z[rt].lsum = z[rt].rsum = z[rt].maxsum = max(z[rt].sum, 0);
        z[rt].cov = 0;
        return ;
    }
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

void update(int rt, int l, int r, int k)
{
    z[rt].sum = (r - l + 1) * k;
    z[rt].lsum = z[rt].rsum = z[rt].maxsum = max(z[rt].sum, 0);
    z[rt].tag = k, z[rt].cov = 1;
}

void pushdown(int rt, int l, int r)
{
    if (!z[rt].cov) return ;
    int mid = (l + r) >> 1;
    update(rt << 1, l, mid, z[rt].tag);
    update(rt << 1 | 1, mid +1, r, z[rt].tag);
    z[rt].tag = z[rt].cov = 0;
}

void modify(int rt, int x, int y, int l, int r, int k)
{
    if (x <= l && r <= y) {
        update(rt, l, r, k);
        return ;
    }
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    if (x <= mid)
        modify(rt << 1, x, y, l, mid, k);
    if (y > mid)
        modify(rt << 1 | 1, x, y, mid + 1, r, k);
    z[rt] = z[rt << 1] + z[rt << 1 | 1];
}

Node query(int rt, int x, int y, int l, int r)
{
    Node L, R;
    if (x <= l && r <= y)
        return z[rt];
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    if (x <= mid)
        L = query(rt << 1, x, y, l, mid);
    if (mid < y)
        R = query(rt << 1 | 1, x, y, mid + 1, r);
    return L + R;
}

void uprange(int x, int y, int k)
{
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) std::swap(x, y);
        modify(1, idx[top[x]], idx[x], 1, n, k);
        x = fa[top[x]];
    }
    if (dep[x] > dep[y]) std::swap(x, y);
    modify(1, idx[x], idx[y], 1, n, k);
}

Node range(int x, int y)
{
    Node L, R;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) {
            R = query(1, idx[top[y]], idx[y], 1, n) + R;
            y = fa[top[y]];
        }
        else {
            L = query(1, idx[top[x]], idx[x], 1, n) + L;
            x = fa[top[x]];
        }
    }
    if (dep[x] <= dep[y])
        R = query(1, idx[x], idx[y], 1, n) + R;
    else
        L = query(1, idx[y], idx[x], 1, n) + L;
    std::swap(L.lsum, L.rsum);
    return L + R;
}

int main()
{
    int x, y;
    read(n);
    for (int i = 1; i <= n; i++) read(val[i]);
    for (int i = 1; i <= n - 1; i++) {
        read(x), read(y);
        add(x, y), add(y, x);
    }
    dfs1(1, 0, 1);
    dfs2(1, 1);
    build(1, 1, n);
    read(m);
    int opt, u, v, w;
    while (m--) {
        read(opt);
        if (opt == 1) {
            read(u), read(v);
            Node ans = range(u, v);
            printf("%d\n", ans.maxsum);
        }
        else {
            read(u), read(v), read(w);
            uprange(u, v, w);
        }
    }
    return 0;
}


同样出自某钟姓神仙之口:“\(GSS\)系列本来只有\(7\)道题,第八道是后面人又加上的。”

再加上蒟蒻最近写数据结构有点恶心,预计将会在
天后将会更新\(GSS8.\)

咕咕咕\(……\)

原文地址:https://www.cnblogs.com/Hydrogen-Helium/p/11737195.html

时间: 2024-08-11 03:11:33

SPOJ GSS系列解题报告的相关文章

SPOJ GSS系列 解题报告

这个系列总共有7道题,目前只做了3道,gss2比较难,gss4是暴力修改,树状数组维护,还没写,gss6和gss7还不在能力范围内. SPOJ_1043 GSS1 题意:给定长度不超过5万的序列,M次查询(貌似没给大小?..),查询所给区间内的最大子段和. 做法:线段树.维护区间和sum,区间可以得到的最大值smax,从区间最左边开始的最大和lmax和右边开始的rmax,然后就可以了.具体更新还是看代码吧.比较巧妙的在于,把query函数的类型设为线段树的节点类型,这样就可以把它的子区间用upd

SPOJ QTREE 系列解题报告

题目一 : SPOJ 375 Query On a Tree http://www.spoj.com/problems/QTREE/ 给一个树,求a,b路径上最大边权,或者修改a,b边权为t. 1 #include <cstdio> 2 #include <iostream> 3 #include <cstring> 4 #include <cstdlib> 5 #include <algorithm> 6 7 using namespace s

SPOJ GSS系列 最大子段和 线段树+树链剖分+splay 1043 1557 1716 2713 2916 4487 6779

最大子段和的各种形式 题解内附每道题的 题意 题目链接 思路 SPOJ 1043 GSS1 静态区间求个最大子段和, 题解 SPOJ 1577 GSS2 和1一样,区别是若区间内存在相同的元素,则该元素只计算一次. 离线一下然后使劲跑.. 题解 SPOJ 1716 GSS3 和1一样,就是要支持单点修改 题解 SPOJ 2713 GSS4 ==普通线段树,感觉和这系列关系不大. 题解 SPOJ 2916 GSS5 题意有点怪,,跟3差不多,就是稍加强了点询问 题解 SPOJ 4487 GSS6

小Z 系列 解题报告

在你谷刷题时偶然发现有这么一个系列,大概\(15\)道题目左右. 而且蒟蒻发现,这个系列的出题人基本上全是LittleZ大佬OrzOrz. 于是心血来潮,想把这个系列全部写完,然后便有了本文. \(P.s.:\)按蒟蒻自己的做题顺序排列,不一定代表难易. \(1.\)小Z的矩阵 因为题目中给出的特征函数\(G(A)\)是对\(2\)取模后的结果,所以很容易就可以发现除对角线以外所有元素对答案的贡献均为\(0\),所以第一步单累计对角线贡献即可. 然后我们发现每翻转一行一列对答案无影响,即刚好翻转

Spring-2-H Array Diversity(SPOJ AMR11H)解题报告及测试数据

Array Diversity Time Limit:404MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Description Here we go! Let's define the diversity of a list of numbers to be the difference between the largest and smallest number in the list. For example, the d

Spring-2-J Goblin Wars(SPOJ AMR11J)解题报告及测试数据

Goblin Wars Time Limit:432MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Description The wizards and witches of Hogwarts School of Witchcraft found Prof. Binn's History of Magic lesson to be no less boring than you found your own history cla

Spring-2-A Magic Grid(SPOJ AMR11A)解题报告及测试数据

Magic Grid Time Limit:336MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Description Thanks a lot for helping Harry Potter in finding the Sorcerer's Stone of Immortality in October. Did we not tell you that it was just an online game ? uhhh!

Spring-2-B Save the Students(SPOJ AMR11B)解题报告及测试数据

Save the Students Time Limit:134MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Description Hogwarts is under attack by the Dark Lord, He-Who-Must-Not-Be-Named. To protect the students, Harry Potter must cast protective spells so that those w

排列组合(permutation)系列解题报告

本文讲解4道关于permutation的题目: 1. Permutation:输出permutation--基础递归 2. Permutation Sequence: 输出字典序排列的第k个permutation--推理3. Next Permutation:给定一个permutation中的序列,求字典序它的下一个permutation是什么--逻辑推理4. Permutation II:和第一题有细微的差别: 对于一个可能有重复元素的数组输出所有permutation--有条件dfs