题目连接:XJOI - NOI2015-13 - Cios
使用神奇的线段树合并在 O(nlogn) 的时间复杂度内解决这道题目。spa
对树上的每一个点都创建一棵线段树,key是时间(即第几回操做),动态开点。blog
线段树的节点维护两个值,一个是这段时间内的 1 操做个数,另外一个是这段时间内变化的黑色节点权值和。递归
在处理全部操做的时候,每棵线段树都是仅表明树上的一个点,所以线段树的每一个节点维护的就是这段时间内以这个点为 a 的 1 操做个数和这段时间内这个点的黑色节点权值和(这个点 x 由黑变白就 -x, 由白变黑就 +x)。get
在处理完全部操做后,咱们进行一次 DFS,自底向上将线段树进行合并。string
目前 DFS(x),先递归处理完 x 的每棵子树,而后枚举 x 的每棵子树,依次将它们的线段树合并到 x 的线段树上。it
如今已经将 x 的前 j-1 棵子树的线段树合并到了 x 的线段树上,如今将第 j 棵子树的线段树合并到 x 的线段树上。io
对于处于 j 子树内的 a 和处于 x 点或前 j-1 棵子树内的黑点,它们的 LCA 就是 x 点,所以他们对 x 的权值有贡献。class
同理,处于 j 子树内的黑点和处于 x 点或前 j-1 棵子树内的 a ,他们的 LCA 也是 x 点,也要计算他们对 x 的权值的贡献。test
一个黑点权值修改会对时间 key 比它大的 1 操做产生影响。
合并时,记合并的两棵线段子树为 (x, y),那么答案就要加上 Son[x][0] 的黑点权值修改 * Son[y][1] 的 1 操做个数。
同理,答案也要加上 Son[y][0] 的黑点权值修改 * Son[x][1] 的 1 操做个数。
而后递归下去合并 (Son[x][0], Son[y][0]) ,合并 (Son[x][1], Son[y][1]),继续计算两边子树内部的答案。
同时注意,这样计算的答案不包括 a 点自己就是一个黑点时贡献的权值,因此要单独加上这个状况的权值。
#include <iostream> #include <cstdlib> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; typedef long long LL; inline void Read(int &Num) { char c = getchar(); bool Neg = false; while (c < '0' || c > '9') { if (c == '-') Neg = true; c = getchar(); } Num = c - '0'; c = getchar(); while (c >= '0' && c <= '9') { Num = Num * 10 + c - '0'; c = getchar(); } if (Neg) Num *= -1; } const int MaxN = 200000 + 5, MaxNode = 7200000 + 5; int n, m, Index; int A[MaxN], Root[MaxN], Son[MaxNode][2], T[MaxNode]; LL Cnt; LL Ans[MaxN], Sum[MaxNode]; struct Edge { int v; Edge *Next; } E[MaxN * 2], *P = E, *Point[MaxN]; inline void AddEdge(int x, int y) { ++P; P -> v = y; P -> Next = Point[x]; Point[x] = P; } inline void Update(int x) { T[x] = T[Son[x][0]] + T[Son[x][1]]; Sum[x] = Sum[Son[x][0]] + Sum[Son[x][1]]; } void Add(int &x, int s, int t, int Pos, int Ds, int Dt) { if (x == 0) x = ++Index; if (s == t) { Sum[x] += (LL)Ds; T[x] += Dt; return; } int m = (s + t) >> 1; if (Pos <= m) Add(Son[x][0], s, m, Pos, Ds, Dt); else Add(Son[x][1], m + 1, t, Pos, Ds, Dt); Update(x); } int Merge(int x, int y, int s, int t) { if (!x) return y; if (!y) return x; if (s == t) { T[x] += T[y]; Sum[x] += Sum[y]; return x; } Cnt += (LL)T[Son[x][1]] * Sum[Son[y][0]]; Cnt += (LL)T[Son[y][1]] * Sum[Son[x][0]]; int m = (s + t) >> 1; Son[x][0] = Merge(Son[x][0], Son[y][0], s, m); Son[x][1] = Merge(Son[x][1], Son[y][1], m + 1, t); Update(x); return x; } void Solve(int x, int Fa) { for (Edge *j = Point[x]; j; j = j -> Next) { if (j -> v == Fa) continue; Solve(j -> v, x); } for (Edge *j = Point[x]; j; j = j -> Next) { if (j -> v == Fa) continue; Cnt = 0; Root[x] = Merge(Root[x], Root[j -> v], 0, m); Ans[x] += Cnt; } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i) { Read(A[i]); if (A[i] != 1) A[i] = 0; } int a, b; for (int i = 1; i < n; ++i) { Read(a); Read(b); AddEdge(a, b); AddEdge(b, a); } for (int i = 1; i <= n; ++i) Add(Root[i], 0, m, 0, A[i] * i, 0); int f, x; for (int i = 1; i <= m; ++i) { Read(f); Read(x); if (f == 1) { Add(Root[x], 0, m, i, 0, 1); if (A[x]) Ans[x] += (LL)x; } else { A[x] ^= 1; if (A[x]) Add(Root[x], 0, m, i, x, 0); else Add(Root[x], 0, m, i, -x, 0); } } Solve(1, 0); for (int i = 1; i <= n; ++i) printf("%lld\n", Ans[i]); return 0; }