1. 程式人生 > >【bzoj3779】重組病毒 LCT+樹上倍增+DFS序+樹狀數組區間修改區間查詢

【bzoj3779】重組病毒 LCT+樹上倍增+DFS序+樹狀數組區間修改區間查詢

父親 輸入 神題 ace 答案 printf 正常 rotate 輸出

題目描述

給出一棵n個節點的樹,每一個節點開始有一個互不相同的顏色,初始根節點為1。 定義一次感染為:將指定的一個節點到根的鏈上的所有節點染成一種新的顏色,代價為這條鏈上不同顏色的數目。 現有m次操作,每次為一下三種之一: RELEASE x:對x執行一次感染; RECENTER x:把根節點改為x,並對原來的根節點執行一次感染; REQUEST x:詢問x子樹中所有節點感染代價的平均值。

輸入

輸入的第一行包含兩個整數n和m,分別代表局域網中計算機的數量,以及操作和詢問的總數。
接下來n-1行,每行包含兩個整數x和y,表示局域網中編號為x和y的計算機之間有網線直接相連。
接下來m行,每行包含一個操作或者詢問,格式如問題描述中所述。

輸出

對於每個詢問,輸出一個實數,代表平均感染時間。輸出與答案的絕對誤差不超過 10^(-6)時才會被視為正確。

樣例輸入

8 6
1 2
1 3
2 8
3 4
3 5
3 6
4 7
REQUEST 7
RELEASE 3
REQUEST 3
RECENTER 5
RELEASE 2
REQUEST 1

樣例輸出

4.0000000000
2.0000000000
1.3333333333


題解

LCT+樹上倍增+DFS序+樹狀數組區間修改區間查詢

這也是一道神題了。。。不查題解真的想不到。。。

首先同種顏色的一定是一條鏈。

然後如果把同種顏色的邊看作實邊,不同種顏色的邊看作虛邊的話,

一次感染就是LCT中的access操作,代價就是虛邊數量+1。。。

(估計出題人是根據access操作的方法才出的這題吧。。。正常人根本想不到是LCT啊。。。)

一次RECENTER操作就是makeroot(makeroot自帶一次access,就對應了題目的再感染一次。。。)

所以一個點的答案只與虛邊數量有關,而只有虛邊變化(access)時某些點的答案才會變化。

考慮加虛邊答案怎麽變化:一個點到其父親節點加了一條虛邊,那麽其子樹內所有節點到根節點都需要經過這一條虛邊,該子樹內答案+1。刪虛邊同理。

這裏要註意一個問題:Splay的根節點不是這條鏈上深度最小的點,最左邊的節點才是要更新子樹的節點。因此在更新子樹時還要找Splay中最左邊的節點,同時不要忘了pushdown。

由於本題的根是變化的,因此參考 bzoj3083遙遠的國度 ,將子樹轉化為dfs序上的至多兩端區間,將得到的區間+1或-1。其中找第一個子節點的過程可以使用樹上倍增實現。

因為本體略卡常,所以采用了樹狀數組區間修改區間查詢,方法參考 bzoj3132上帝造題的七分鐘 。

查詢時同理,直接把區間和求出來即可。

時間復雜度$O(n\log^2n*不知多大的常數)=O(能過)$

#include <cstdio>
#include <algorithm>
#define N 100010
using namespace std;
typedef long long ll;
struct lct
{
	int fa , c[2] , rev;
}a[N];
int n , head[N] , to[N << 1] , next[N << 1] , cnt , fa[N][20] , deep[N] , pos[N] , last[N] , tot , log[N] , root = 1;
ll f[N] , g[N];
char str[15];
inline void modify(int x , int a)
{
	int i;
	for(i = x ; i <= n ; i += i & -i) f[i] += a , g[i] += x * a;
}
inline ll query(int x)
{
	int i;
	ll s1 = 0 , s2 = 0;
	for(i = x ; i ; i -= i & -i) s1 += f[i] , s2 += g[i];
	return (x + 1) * s1 - s2;
}
inline void add(int x , int y)
{
	to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x)
{
	int i;
	pos[x] = ++tot;
	for(i = 1 ; (1 << i) <= deep[x] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(i = head[x] ; i ; i = next[i])
		if(to[i] != fa[x][0])
			fa[to[i]][0] = x , a[to[i]].fa = x , deep[to[i]] = deep[x] + 1 , dfs(to[i]);
	last[x] = tot;
	modify(pos[x] , 1) , modify(last[x] + 1 , -1);
}
inline int find(int x , int y)
{
	int i;
	for(i = log[deep[x] - deep[y]] ; ~i ; i -- )
		if((1 << i) < deep[x] - deep[y])
			x = fa[x][i];
	return x;
}
inline void rever(int x)
{
	swap(a[x].c[0] , a[x].c[1]) , a[x].rev ^= 1;
}
inline void pushdown(int x)
{
	if(a[x].rev) rever(a[x].c[0]) , rever(a[x].c[1]) , a[x].rev = 0;
}
inline bool isroot(int x)
{
	return a[a[x].fa].c[0] != x && a[a[x].fa].c[1] != x;
}
void update(int x)
{
	if(!isroot(x)) update(a[x].fa);
	pushdown(x);
}
inline void rotate(int x)
{
	int y = a[x].fa , z = a[y].fa , l = (a[y].c[1] == x) , r = l ^ 1;
	if(!isroot(y)) a[z].c[a[z].c[1] == y] = x;
	a[x].fa = z , a[y].fa = x , a[a[x].c[r]].fa = y , a[y].c[l] = a[x].c[r] , a[x].c[r] = y;
}
inline void splay(int x)
{
	int y , z;
	update(x);
	while(!isroot(x))
	{
		y = a[x].fa , z = a[y].fa;
		if(!isroot(y)) rotate((a[y].c[0] == x) ^ (a[z].c[0] == y) ? x : y);
		rotate(x);
	}
}
inline void treemodify(int x , int v)
{
	if(!x) return;
	int y;
	while(a[x].c[0]) pushdown(x) , x = a[x].c[0];
	if(root == x) modify(1 , v);
	else if(pos[root] < pos[x] || pos[root] > last[x]) modify(pos[x] , v) , modify(last[x] + 1 , -v);
	else y = find(root , x) , modify(1 , v) , modify(pos[y] , -v) , modify(last[y] + 1 , v);
}
inline double treequery(int x)
{
	int y;
	if(root == x) return (double)query(n) / n;
	else if(pos[root] < pos[x] || pos[root] > last[x]) return (double)(query(last[x]) - query(pos[x] - 1)) / (last[x] - pos[x] + 1);
	else
	{
		y = find(root , x);
		return (double)(query(n) - query(last[y]) + query(pos[y] - 1)) / (n - last[y] + pos[y] - 1);
	}
}
inline void access(int x)
{
	int t = 0 , y;
	while(x) splay(x) , treemodify(t , -1) , y = a[x].c[1] , a[x].c[1] = t , treemodify(y , 1) , t = x , x = a[x].fa;
}
inline void makeroot(int x)
{
	access(x) , splay(x) , rever(x) , root = x;
}
int main()
{
	int m , i , x , y;
	scanf("%d%d" , &n , &m);
	for(i = 2 ; i <= n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x) , log[i] = log[i >> 1] + 1;
	dfs(1);
	while(m -- )
	{
		scanf("%s%d" , str , &x);
		if(str[2] == ‘L‘) access(x);
		else if(str[2] == ‘C‘) makeroot(x);
		else printf("%.10lf\n" , treequery(x));
	}
	return 0;
}

【bzoj3779】重組病毒 LCT+樹上倍增+DFS序+樹狀數組區間修改區間查詢