JZOJ4753【GDOI2017模拟9.4】种树 LCT维护子树信息+换根时维护Dfs序(CC MONOPLOY加强版)

题目大意

给定一颗 N 个节点的有根树,初始时每个叶子节点都有一个不一样的颜色。定义一个节点的代价为其走到根遇到的不同颜色种数。有M次询问,共有三种类型:
1.将节点 u 到根的路径上的所有点的颜色改成一种新的颜色。
2.将树根改为节点u,同时将两个根之间的路径上所有点改为一种颜色。
3.查询节点 u 子树里所有节点代价的平均数。

N,M106

解题思路

我们先考虑一个子问题,假设没有第二个操作,我们应该怎么维护?

如果没有第二个操作,那么树的根是确定的,也就是说树的DFS序是确定的。我们发现对于第一种操作很像 LCT 中的 Access 操作。所以我们考虑用 LCT 来维护。那么模型就可以转化为:虚边的权值为1,实边的权值为0,初始时全部变都是实边,一个节点 u 的代价就是到遇到的虚边数+1。那么每次Access操作我们就需要把一些虚边改成实边,把一些实边改成虚边。

1.对于把虚边改成实边:在 Access 时假设当前已合并的平衡树中最上端的点的父亲为 v ,如果v的下方有节点也处于 v 所在的平衡树中,那么这棵平衡树内就要把一条实边转成虚边,也就是把v下端,最上方的点打上 +1 tag

2.对于把实边改成虚边:就是 Access 每次扩大平衡树时都会把一条虚边改成实边,那么只要把当前平衡树中最上端的点打上 1 tag

至于维护区间和,只要以DFS序为下标用线段树维护。(这就是CC MONOPLOY)。

那么我们考虑又换个操作的情况,如果有了换根操作,那么DFS序就会乱,线段树维护的值就会有问题。那么我们考虑不改变DFS序,只是在打 tag 的是后考虑一下在原树中根 root 与当前结点 v 的关系。

1.v=root:查询整棵树。

2. v root的子树内或 x root不存在包含关系:直接查询原DFS区间。

3. root v 的子树内。设p v 的所有儿子中是root祖先的那个儿子。那么此时 v 的子树应为原树中v的子树外所有的节点、 v 本身以及v中非 p <script type="math/tex" id="MathJax-Element-39">p</script>的所有儿子的子树。

那么每次查询和修改再加以判断一下就可以维护换根操作。

程序

//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, Root, D[MAXN], L[MAXN], R[MAXN], Deep[MAXN], Ord[MAXN], Tag[MAXN * 4], Fa[MAXN][20];
int tot, Next[MAXN * 2], Last[MAXN], Go[MAXN * 2], Pre[MAXN], Son[MAXN][2], Rev[MAXN];
bool Flag[MAXN];
LL Sum[MAXN * 4];

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 Reverse(int u) {
    if (Rev[u]) {
        Rev[Son[u][0]] ^= 1, Rev[Son[u][1]] ^= 1;
        swap(Son[u][0], Son[u][1]);
        Rev[u] = 0;
    }
}

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) {
    Tag[Now] += Val, Sum[Now] += LL(r - l + 1) * Val; 
}

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

void Modify(int x, int l, int r, int lx, int rx, int Val) {
    if (lx > rx) return;
    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);
    Sum[x] = Sum[x * 2] + Sum[x * 2 + 1];
}

LL Query(int x, int l, int r, int lx, int rx) {
    if (lx > rx) return 0;
    if (l == lx && r == rx) return Sum[x];
    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);
}

int Chk(int l, int r) {
    return max(r - l + 1, 0);
}

int GetDel(int Now, int Goal) {
    for (int i = 19; i + 1; i --)
        if (Deep[Fa[Now][i]] > Deep[Goal]) Now = Fa[Now][i];
    return Now;
}

double Query(int u) {
    LL Ans = 0;
    int Num = 0;
    if (u == Root || L[Root] < L[u] || L[Root] > R[u]) Ans += Query(1, 1, N, L[u], R[u]), Num += Chk(L[u], R[u]); else {
        Ans += Query(1, 1, N, 1, L[u] - 1), Num += Chk(1, L[u] - 1);
        Ans += Query(1, 1, N, R[u] + 1, N), Num += Chk(R[u] + 1, N);
        int Del = GetDel(Root, u);
        Ans += Query(1, 1, N, L[u], L[Del] - 1), Num += Chk(L[u], L[Del] - 1);
        Ans += Query(1, 1, N, R[Del] + 1, R[u]), Num += Chk(R[Del] + 1, R[u]);
    }
    return 1.0 * Ans / (1.0 * Num);
}

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) {
    static int D[MAXN], top;
    D[top = 1] = Now;
    for (int p = Now; !IsRoot(p); p = Pre[p]) D[++ top] = Pre[p];
    for (; top; top --) Reverse(D[top]);
    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) {
    Reverse(Now);
    for (; Son[Now][0]; Now = Son[Now][0], Reverse(Now));
    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]);
            if (S == Root || L[Root] < L[S] || L[Root] > R[S]) Modify(1, 1, N, L[S], R[S], 1); else {
                Modify(1, 1, N, 1, L[S] - 1, 1);
                Modify(1, 1, N, R[S] + 1, N, 1);
                int Del = GetDel(Root, S);
                Modify(1, 1, N, L[S], L[Del] - 1, 1);
                Modify(1, 1, N, R[Del] + 1, R[S], 1);
            }

        };
        if (t) {
            int S = GetSon(t);
            if (S == Root || L[Root] < L[S] || L[Root] > R[S]) Modify(1, 1, N, L[S], R[S], -1); else {
                Modify(1, 1, N, 1, L[S] - 1, -1);
                Modify(1, 1, N, R[S] + 1, N, -1);
                int Del = GetDel(Root, S);
                Modify(1, 1, N, L[S], L[Del] - 1, -1);
                Modify(1, 1, N, R[Del] + 1, R[S], -1);
            }
        }
    }
}

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

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;
                Fa[v][0] = u;
            }
        } 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 --;
        }
    }

    for (int j = 1; j < 20; j ++)
        for (int i = 1; i <= N; i ++)
            Fa[i][j] = Fa[Fa[i][j - 1]][j - 1];
    Build(1, 1, N);
}

void MakeRoot(int u) {
    Access(u), Splay(u);
    Rev[u] ^= 1, Root = u;
}

int main() {
    scanf("%d%d", &N, &Q);
    for (int i = 1; i < N; i ++) {
        int u, v;
        Read(u), Read(v);
        Link(u, v), Link(v, u);
    }
    Preparation();
    Root = 1;
    for (int i = 1; i <= Q; i ++) {
        int u, Ord;
        Read(Ord), Read(u);
        if (Ord == 3) printf("%.8f\n", Query(u)); 
        if (Ord == 1) Access(u);
        if (Ord == 2) MakeRoot(u);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值