• 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 << '
    ';
            else cout << ans.maxsum << '
    ';
        }
        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
    ", 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 << '
    ';
                else cout << ans.maxsum << '
    ';
            }
        }
        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:
    ",++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
    ", 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
    ", 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
    ", 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
    ", 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
    ", ans.maxsum);
            }
            else {
                read(u), read(v), read(w);
                uprange(u, v, w);
            }
        }
        return 0;
    }
    

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

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

    咕咕咕(……)

  • 相关阅读:
    linux命令行 238
    C语言语法分析器 238
    有穷自动机的构造与识别 238
    简单词法分析实验报告238许锦沛 238
    javascript获得鼠标的坐标值
    滚动条颜色设置(IE、chrome浏览器)
    获取元素的页面位置,兼容各浏览器
    js闭包的作用
    javascrpt绑定事件之匿名函数
    javascript 函数及作用域
  • 原文地址:https://www.cnblogs.com/Hydrogen-Helium/p/11737195.html
Copyright © 2020-2023  润新知