CodeChef MONOPOLY 基于Access性质的线段树维护

题目大意

给你 N 个点组成的一棵树,标号为 0 ~ N1 且编号为 0 的点为根。每个点都有一种颜色,一开始 N 个点的颜色都不相同。
距离的定义:如果两个相邻的节点颜色一样则两点间的距离为 0 ,否则为 1
现在给你 Q 个操作,每个操作给你一个字符和一个数字 Ordu
Ord=O :把编号为 u 的点到根路径上的所有点染成一种新的颜色。
Ord=q :询问以 u 为根的子树中的所有节点到根的距离和。

N,Q<=150000

解题思路

首先我们可以发现,相同颜色的节点一定是连续一段的一条链,且方向是朝向根的。那么一个显然的性质就是对于一个节点,最多只会有一个儿子的颜色与它相同。那现在就讨论一下修改颜色时情况。

我们不妨设当前节点为 u ,他的父亲节点为 v ,节点i的颜色为 Coli
Colu=Colv ,那么显然对答案是没有影响的。
ColuColv , 假设 u 有一个的儿子 p ,满足 Colu=Colp ,那么会产生影响的就只有 p,v 两颗子树。我们再用以颗线段树维护每个节点到根的距离。那么这个操作就是对 u,p 的整颗子树 +1,1 。当我们用 DFS 序表示时就变成区间修改了。那么到目前我们就处理完了一个个往上跳的情况。

那么怎么快速的修改呢。看到这种修改某个节点到根路径上所有点信息时,我们和自然的就想到了 LCT Access 操作。那我们是否可以用 LCT 来维护呢?

我们惊讶地发现,在 LCT 中一颗 Splay 上的节点肯定是被 Access 过的,他们的颜色肯定是相等的。也就是说我们一颗 Splay 中只需要处理最下面的一个节点就可以了。所以在 Access 时,每次对合并两颗 Splay ,只有相交处的节点需要修改,做上述的区间修改操作就可以快速完成了。

程序

//CodeChef MONOPOLY YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 2e5;
typedef long long LL;

struct Tree {int Tag; LL Sum;} Tr[MAXN * 4];

int N, Q, cnt, D[MAXN], L[MAXN], R[MAXN], Deep[MAXN], Ord[MAXN];
int tot, Next[MAXN * 2], Last[MAXN], Go[MAXN * 2], Pre[MAXN], Son[MAXN][2];
bool Flag[MAXN];

void Read(int &Now) {
    char c; 
    while (c = getchar(), c < '0' || c > '9');
    Now = c - 48;
    while (c = getchar(), c >= '0' && c <= '9') Now = Now * 10 + c - 48;
}

void Link(int u, int v) {
    Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v;
}

void Update(int Now, int l, int r, int Val) {
    Tr[Now].Tag += Val, Tr[Now].Sum += LL(r - l + 1) * Val; 
}

void Push(int x, int l, int r) {
    int Mid = (l + r) >> 1;
    Update(x * 2, l, Mid, Tr[x].Tag), Update(x * 2 + 1, Mid + 1, r, Tr[x].Tag);
    Tr[x].Tag = 0;
}   

void Modify(int x, int l, int r, int lx, int rx, int Val) {
    if (l == lx && r == rx) {
        Update(x, l, r, Val);
        return;
    }
    Push(x, l, r);
    int Mid = (l + r) >> 1;
    if (rx <= Mid) Modify(x * 2, l, Mid, lx, rx, Val); else
    if (lx > Mid) Modify(x * 2 + 1, Mid + 1, r, lx, rx, Val); else
        Modify(x * 2, l, Mid, lx, Mid, Val), Modify(x * 2 + 1, Mid + 1, r, Mid + 1, rx, Val);
    Tr[x].Sum = Tr[x * 2].Sum + Tr[x * 2 + 1].Sum;
}

LL Query(int x, int l, int r, int lx, int rx) {
    if (l == lx && r == rx) return Tr[x].Sum;
    Push(x, l, r);
    int Mid = (l + r) >> 1;
    if (rx <= Mid) return Query(x * 2, l, Mid, lx, rx); else
    if (lx > Mid) return Query(x * 2 + 1, Mid + 1, r, lx, rx); else
        return Query(x * 2, l, Mid, lx, Mid) + Query(x * 2 + 1, Mid + 1, r, Mid + 1, rx);
}

bool IsRoot(int Now) {return Son[Pre[Now]][0] != Now && Son[Pre[Now]][1] != Now;}

void Rotate(int Now) {
    int Fa = Pre[Now], Gran = Pre[Fa], Side = (Son[Fa][1] == Now);
    if (!IsRoot(Fa)) Son[Gran][Son[Gran][1] == Fa] = Now;
    Pre[Now] = Gran, Pre[Fa] = Now;
    Son[Fa][Side] = Son[Now][!Side], Pre[Son[Fa][Side]] = Fa;
    Son[Now][!Side] = Fa;
}

void Splay(int Now) {
    for (; !IsRoot(Now); Rotate(Now)) {
        int Fa = Pre[Now], Gran = Pre[Fa];
        if (IsRoot(Fa)) continue;
        (Son[Fa][1] == Fa) ^ (Son[Gran][1] == Fa) ? Rotate(Now) : Rotate(Fa);
    }
}

int GetSon(int Now) {
    for (; Son[Now][0]; Now = Son[Now][0]);
    return Now;
}

void Access(int Now) {
    for (int t = 0; Now; Son[Now][1] = t, t = Now, Now = Pre[Now]) {
        Splay(Now);
        if (Son[Now][1]) {
            int S = GetSon(Son[Now][1]);
            Modify(1, 1, N, L[S], R[S], 1);
        };
        if (t) {
            int S = GetSon(t);
            Modify(1, 1, N, L[S], R[S], -1);
        }
    }
}

void Build(int x, int l, int r) {
    if (l == r) {
        Tr[x].Sum = Deep[Ord[l]];
        return;
    }
    int Mid = (l + r) >> 1;
    Build(x * 2, l, Mid), Build(x * 2 + 1, Mid + 1, r);
    Tr[x].Sum = Tr[x * 2].Sum + Tr[x * 2 + 1].Sum;
}

void Preparation() {
    int top = 1; D[1] = 1;
    while (top) {
        int u = D[top];
        if (!Flag[u]) {
            Flag[u] = 1, L[u] = R[u] = ++ cnt; Ord[cnt] = u;
            for (int p = Last[u]; p; p = Next[p]) {
                int v = Go[p];
                if (L[v]) continue;
                D[++ top] = v, Pre[v] = u, Deep[v] = Deep[u] + 1;
            }
        } else {
            for (int p = Last[u]; p; p = Next[p]) 
                if (L[Go[p]] > L[u]) R[u] = max(R[u], R[Go[p]]);
            top --;
        }
    }
    Build(1, 1, N);
}

int main() {
    scanf("%d", &N);
    for (int i = 1; i < N; i ++) {
        int u, v;
        Read(u), Read(v);
        u ++, v ++;
        Link(u, v), Link(v, u);
    }
    Preparation();
    scanf("%d", &Q);
    for (int i = 1; i <= Q; i ++) {
        char c; int u;
        while (c = getchar(),c != 'q' && c != 'O');
        Read(u);
        u ++;
        if (c == 'q') printf("%.8f\n", Query(1, 1, N, L[u], R[u]) / double(R[u] - L[u] + 1)); else Access(u);
    }
}

你可能感兴趣的:(CodeChef MONOPOLY 基于Access性质的线段树维护)