[XJOI NOI2015模擬題13] C 白黑樹 【線段樹合併】

題目連接: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;
}
相關文章
相關標籤/搜索