CF1491H Yuezheng Ling and Dynamic Tree

传送门

这真是一道分块神题!


思路

我们先将点编号进行分块

\(b[i]\) 表示 \(i\) 的祖先中,最近的不与 \(i\) 同一个块的结点编号

显然,如果 \(pos[a[i]]<pos[i]\),那么 \(b[i] = a[i]\);否则 \(b[i] = b[a[i]]\)\(pos[i]\) 表示 \(i\) 所在的块的编号)

考虑一个修改如何进行:

首先,对于零碎的块,我们直接暴力修改 \(a[i]\)\(b[i]\)

对于完整的块,我们很难对块进行标记

但我们发现,当 \(pos[a[i]]<pos[i]\) 时,由于 \(a[i]\)\(b[i]\) 相等,\(b[i]\) 就可以直接减去 \(x\)

对于一个块,要使里面的元素都有 \(a[i]=b[i]\),那么最多只需要减 \(B\) 次(\(B\) 表示块的大小)

因此,我们考虑用 \(cnt[i]\) 表示第 \(i\) 个块整体已经减了的数量,如果 \(cnt[i]<B\),那么我们就对块中每个结点暴力修改;否则,我们将 \(x\) 加到 \(tag[i]\) 中,表示 \(i\) 块内的结点的 \(b\)直接减去的数值

这样就将复杂度依旧均摊到 \(O(n\sqrt n)\)

对于查询,方法类似于树剖求 \(LCA\) 的方法:

  1. \(pos[x]\not=pos[y]\) 时,\(x\)\(y\) 中编号大的跳到 \(b\)

  2. \(pos[x]\not=pos[y]\)\(b[x]\not= b[y]\) 时,\(x\)\(b[x]\)\(y\)\(b[y]\)

  3. \(pos[x]\not=pos[y]\)\(b[x]= b[y]\) 时,\(x\)\(y\) 编号大的跳到自己的父亲处

单次询问复杂度 \(O(\sqrt n)\)

(注意,\(tag\) 有可能爆 int!)


代码

#include<iostream>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#define LL long long
#define FOR(i, x, y) for(int i = (x); i <= (y); i++)
#define ROF(i, x, y) for(int i = (x); i >= (y); i--)
#define PFOR(i, x) for(int i = he[x]; i; i = r[i].nxt)
inline int reads()
{
    int sign = 1, re = 0; char c = getchar();
    while(c < '0' || c > '9'){if(c == '-') sign = -1; c = getchar();}
    while('0' <= c && c <= '9'){re = re * 10 + (c - '0'); c = getchar();}
    return sign * re;
}
const int B = 310;
const int K = 100000 / B + 5;
int n, q, a[100005], b[100005];
int pos[100005], L[K], R[K];
int cnt[K], tag[K];
inline int fa(int x) {return std::max(a[x] - tag[pos[x]], 1);}
inline int jp(int x) {return std::max(b[x] - tag[pos[x]], 1);}
inline int LCA(int x, int y)
{
    while(x != y)
    {
        if(pos[x] < pos[y]) y = jp(y);
        else if(pos[x] > pos[y]) x = jp(x);
        else
        {
            if(jp(x) != jp(y)) x = jp(x), y = jp(y);
            else x > y ? x = fa(x) : y = fa(y);
        }
    }
    return x;
}
inline void modify(int l, int r, int x)
{
    FOR(i, pos[l] + 1, pos[r] - 1)
    {
        if(cnt[i] + x <= B)
        {
            FOR(j, L[i], R[i])
                a[j] = std::max(1, a[j] - x),
                b[j] = pos[a[j]] < pos[j] ? a[j] : b[a[j]];
            cnt[i] += x;
        }
        else if(cnt[i] < B)
        {
            int sub = B - cnt[i];
            FOR(j, L[i], R[i])
                a[j] = std::max(1, a[j] - sub),
                b[j] = pos[a[j]] < pos[j] ? a[j] : b[a[j]];
            cnt[i] = B;
            tag[i] += x - sub, tag[i] = std::min(tag[i], n);
        }
        else tag[i] += x, tag[i] = std::min(tag[i], n);
    }
    if(pos[l] < pos[r])
    {
        FOR(i, l, R[pos[l]])
            a[i] = std::max(1, a[i] - x),
            b[i] = pos[a[i]] < pos[i] ? a[i] : b[a[i]];
        FOR(i, L[pos[r]], r)
            a[i] = std::max(1, a[i] - x),
            b[i] = pos[a[i]] < pos[i] ? a[i] : b[a[i]];
    }
    else
    {
        FOR(i, l, r)
            a[i] = std::max(1, a[i] - x),
            b[i] = pos[a[i]] < pos[i] ? a[i] : b[a[i]];
    }
    FOR(i, r + 1, R[pos[r]])
        b[i] = pos[a[i]] < pos[i] ? a[i] : b[a[i]];
}
signed main()
{
#ifndef ONLINE_JUDGE
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    n = reads(), q = reads(), a[1] = pos[1] = 1;
    FOR(i, 2, n) a[i] = reads(), pos[i] = (i - 1) / B + 1, b[i] = pos[a[i]] < pos[i] ? a[i] : b[a[i]];
    FOR(i, 1, pos[n]) L[i] = R[i - 1] + 1, R[i] = R[i - 1] + B;
    R[pos[n]] = n;
    FOR(i, 1, q)
    {
        int ty = reads();
        if(ty == 1)
        {
            int l = reads(), r = reads(), x = reads();
            modify(l, r, x);
        }
        else
        {
            int u = reads(), v = reads();
            printf("%d\n", LCA(u, v));
        }
    }
    return 0;
}
posted @ 2022-09-02 20:31  zuytong  阅读(14)  评论(0编辑  收藏  举报