1. 程式人生 > >【bzoj1095】[ZJOI2007]Hide 捉迷藏 動態樹分治+堆

【bzoj1095】[ZJOI2007]Hide 捉迷藏 動態樹分治+堆

max 插入 fine 時間 結構 答案 += oot tdi

題目描述

捉迷藏 Jiajia和Wind是一對恩愛的夫妻,並且他們有很多孩子。某天,Jiajia、Wind和孩子們決定在家裏玩捉迷藏遊戲。他們的家很大且構造很奇特,由N個屋子和N-1條雙向走廊組成,這N-1條走廊的分布使得任意兩個屋子都互相可達。遊戲是這樣進行的,孩子們負責躲藏,Jiajia負責找,而Wind負責操縱這N個屋子的燈。在起初的時候,所有的燈都沒有被打開。每一次,孩子們只會躲藏在沒有開燈的房間中,但是為了增加刺激性,孩子們會要求打開某個房間的電燈或者關閉某個房間的電燈。為了評估某一次遊戲的復雜性,Jiajia希望知道可能的最遠的兩個孩子的距離(即最遠的兩個關燈房間的距離)。 我們將以如下形式定義每一種操作: C(hange) i 改變第i個房間的照明狀態,若原來打開,則關閉;若原來關閉,則打開。 G(ame) 開始一次遊戲,查詢最遠的兩個關燈房間的距離。

輸入

第一行包含一個整數N,表示房間的個數,房間將被編號為1,2,3…N的整數。接下來N-1行每行兩個整數a, b,表示房間a與房間b之間有一條走廊相連。接下來一行包含一個整數Q,表示操作次數。接著Q行,每行一個操作,如上文所示。

輸出

對於每一個操作Game,輸出一個非負整數到hide.out,表示最遠的兩個關燈房間的距離。若只有一個房間是關著燈的,輸出0;若所有房間的燈都開著,輸出-1。

樣例輸入

8
1 2
2 3
3 4
3 5
3 6
6 7
6 8
7
G
C 1
G
C 2
G
C 1
G

樣例輸出

4
3
3
4


題解

動態樹 分治 動態 樹分治 +堆

動態點(樹)分治:將點分治的上一層重心與下一層連邊,可以得到一棵新樹(點分樹)。由於每次都是找重心,所以樹高不超過$\log$,就可以使用各種數據結構維護各種子樹信息。

考慮如果本題是靜態的,只有一次查詢該怎麽做:求出以每個點為根的最長路徑,即求 $|$所有節點的 $|$子節點的 $|$子樹中的節點到父親節點的最大值$|$ 的最大值和次大值的和$|$ 的最大值$|$。($|$為斷句方法= =)

形象一點,求每個點的子樹中的所有節點到父親節點的距離的最大值$p1$;每個點求出它所有子節點的$p1$以及當前節點狀態(存在則為0)中的最大的和次大的,加起來得到$p2$;所有節點的$p2$的最大值就是$p3$。

考慮帶修改,多次查詢:首先由於有修改,所以樹高必須要有保證,所以選擇動態樹分治的點分樹結構。

那麽需要是用數據結構,支持查詢最大值和次大值,使用3種堆:

$s1[]$:維護一個子樹中所有節點到當前點的父親節點的距離;

$s2[]$:維護一個點的所有子分治節點(點分樹中子節點)的$s1$中的最大值,如果當前節點可用,則需要再增加一個$0$;

$s3$:維護所有節點的$s2$的最大值與次大值(如果存在)之和。

每次$s3$的最大值就是答案。

於是就可以自底向上修改路徑上的$s1$和$s2$,並修改$s3$。具體實現較為復雜:需要消除下一級對上一級的影響,所以要先刪除上一級,再插入上一級;需要實現可以刪除的堆,於是需要維護兩個堆,刪除時將要刪的數加入到輔助堆中,每次取堆頂時如果兩堆堆頂相同則都彈出。

並且需要維護歐拉遍歷序並使用RMQLCA支持$O(1)$查詢LCA以保證時間復雜度。

總時間復雜度為$O(n\log^2n)$,空間復雜度為$O(n\log n)$。

#include <queue>
#include <cstdio>
#define N 100010
using namespace std;
struct heap
{
	priority_queue<int> A , B;
	void push(int x) {A.push(x);}
	void del(int x) {B.push(x);}
	int top()
	{
		while(!B.empty() && A.top() == B.top()) A.pop() , B.pop();
		return A.top();
	}
	int sum()
	{
		int a = top(); A.pop();
		int b = top(); push(a);
		return a + b;
	}
	int size() {return A.size() - B.size();}
}s1[N] , s2[N] , s3;
int head[N] , to[N << 1] , next[N << 1] , cnt , vis[N] , deep[N] , pos[N] , md[20][N << 1];
int si[N] , mx[N] , sum , root , log[N << 1] , tot , fa[N] , val[N];
char str[5];
void insert(heap &s) {if(s.size() >= 2) s3.push(s.sum());}
void erase(heap &s) {if(s.size() >= 2) s3.del(s.sum());}
void add(int x , int y)
{
	to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x , int fa)
{
	int i;
	md[0][++tot] = deep[x] , pos[x] = tot;
	for(i = head[x] ; i ; i = next[i])
		if(to[i] != fa)
			deep[to[i]] = deep[x] + 1 , dfs(to[i] , x) , md[0][++tot] = deep[x];
}
int lca(int x , int y)
{
	x = pos[x] , y = pos[y];
	if(x > y) swap(x , y);
	int k = log[y - x + 1];
	return min(md[k][x] , md[k][y - (1 << k) + 1]);
}
void getroot(int x , int fa)
{
	int i;
	si[x] = 1 , mx[x] = 0;
	for(i = head[x] ; i ; i = next[i])
		if(!vis[to[i]] && to[i] != fa)
			getroot(to[i] , x) , si[x] += si[to[i]] , mx[x] = max(mx[x] , si[to[i]]);
	mx[x] = max(mx[x] , sum - si[x]);
	if(mx[x] < mx[root]) root = x;
}
void solve(int x)
{
	int i;
	vis[x] = 1;
	for(i = head[x] ; i ; i = next[i])
		if(!vis[to[i]])
			sum = si[to[i]] , root = 0 , getroot(to[i] , 0) , fa[root] = x , solve(root);
}
void join(int x)
{
	erase(s2[x]) , s2[x].push(0) , insert(s2[x]);
	int t;
	for(t = x ; fa[t] ; t = fa[t])
	{
		erase(s2[fa[t]]);
		if(s1[t].size()) s2[fa[t]].del(s1[t].top());
		s1[t].push(deep[fa[t]] + deep[x] - 2 * lca(fa[t] , x)) , s2[fa[t]].push(s1[t].top());
		insert(s2[fa[t]]);
	}
}
void remove(int x)
{
	erase(s2[x]) , s2[x].del(0) , insert(s2[x]);
	int t;
	for(t = x ; fa[t] ; t = fa[t])
	{
		erase(s2[fa[t]]);
		s2[fa[t]].del(s1[t].top()) , s1[t].del(deep[fa[t]] + deep[x] - 2 * lca(fa[t] , x));
		if(s1[t].size()) s2[fa[t]].push(s1[t].top());
		insert(s2[fa[t]]);
	}
}
int main()
{
	int n , m , i , j , x , y , num;
	scanf("%d" , &n) , num = n;
	for(i = 1 ; i < n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x);
	dfs(1 , 0);
	for(i = 2 ; i <= tot ; i ++ ) log[i] = log[i >> 1] + 1;
	for(i = 1 ; (1 << i) <= tot ; i ++ )
		for(j = 1 ; j <= tot - (1 << i) + 1 ; j ++ )
			md[i][j] = min(md[i - 1][j] , md[i - 1][j + (1 << (i - 1))]);
	mx[0] = 1 << 30 , sum = n , getroot(1 , 0) , solve(root);
	for(i = 1 ; i <= n ; i ++ ) val[i] = 1 , join(i);
	scanf("%d" , &m);
	while(m -- )
	{
		scanf("%s" , str);
		if(str[0] == ‘G‘)
		{
			if(num >= 2) printf("%d\n" , s3.top());
			else printf("%d\n" , num - 1);
		}
		else
		{
			scanf("%d" , &x);
			if(val[x]) num -- , val[x] = 0 , remove(x);
			else num ++ , val[x] = 1 , join(x);
		}
	}
	return 0;
}

【bzoj1095】[ZJOI2007]Hide 捉迷藏 動態樹分治+堆