[ZJOI2007]Hide 捉迷藏

原文链接:https://blog.csdn.net/qq_41552508/article/details/101171460
  捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩
捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋
子都互相可达。游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的
时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要
求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两
个孩子的距离(即最远的两个关灯房间的距离)。 我们将以如下形式定义每一种操作: C(hange) i 改变第i个房
间的照明状态,若原来打开,则关闭;若原来关闭,则打开。 G(ame) 开始一次游戏,查询最远的两个关灯房间的
距离。

Input

  第一行包含一个整数N,表示房间的个数,房间将被编号为1,2,3…N的整数。接下来N-1行每行两个整数a, b,
表示房间a与房间b之间有一条走廊相连。接下来一行包含一个整数Q,表示操作次数。接着Q行,每行一个操作,如
上文所示。
N ≤100000, M ≤500000。

Output

  对于每一个操作Game,输出一个非负整数到hide.out,表示最远的两个关灯房间的距离。若只有一个房间是关
着灯的,输出0;若所有房间的灯都开着,输出-1。

Sample Input

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
Sample Output

4
3
3
4


这道题的做法有括号序列、动态点分治、线段树维护直径。此处只介绍线段树维护直径的做法。

首先我们求一个 dfs序,然后在dfs序上建线段树,对于线段树的每个区间,我们维护两个点表示在这个区间中相距最远的两个0点。区间合并时我们只需要取出这两个区间所维护的点,然后对这四个点两两求距离更新答案即可。

这样做的原因在于对于两个子树来说,每个子树都有一条属于该子树的直径,则两个子树合并后的新直径必定是从原来两个子树中的4个点中选取2个点作为答案。

大致思路就是这样,具体细节见代码。

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
typedef long long ll;
const int N = 1e5+100;
using namespace std;

int n,m,dis[N],tot,head[N],f[N][25],dfn[N],rk[N],flag[N],T;
pair<int,int> sgt[4*N];
struct Edge{
	int to,next;
}e[2*N];

inline void add(int x,int y){
	e[++tot].to = y, e[tot].next = head[x], head[x] = tot;
}

void dfs(int x,int fa){
	dis[x] = dis[fa]+1; f[x][0] = fa; dfn[x] = ++tot; rk[tot] = x;
	for(int i = 1; (1<<i) <= dis[x]; i++)
		f[x][i] = f[f[x][i-1]][i-1];
	for(int i = head[x]; i; i = e[i].next){
		int y = e[i].to;
		if(y == fa) continue;
		dis[y] = dis[x]+1; dfs(y,x);
	}
}

inline int lca(int x,int y){
	if(dis[x] > dis[y]) swap(x,y);
	for(int i = T; i >= 0; i--)
		if(dis[f[y][i]] >= dis[x]) y = f[y][i];
	if(x == y) return x;
	for(int i = T; i >= 0; i--)
		if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}

inline int dist(int x,int y){
	if(x == 0 && y == 0) return -1;
	if(x == 0 || y == 0) return 0;
	return dis[x]+dis[y]-2*dis[lca(x,y)];
}

inline pair<int,int> pushUp(pair<int,int> x,pair<int,int> y){
	int a[5],res = 0,cnt = 0,tmp; pair<int,int> base = make_pair(0,0); a[0] = 0;
	if(x.first && flag[x.first]) a[++cnt] = x.first;
	if(x.second && flag[x.second]) a[++cnt] = x.second;
	if(y.first && flag[y.first]) a[++cnt] = y.first;
	if(y.second && flag[y.second]) a[++cnt] = y.second;
	rep(i,1,cnt-1)
		rep(j,i+1,cnt)
			if((tmp=dist(a[i],a[j])) > res)
				res = tmp, base = make_pair(a[i],a[j]);
	if(res == 0) base = make_pair(a[cnt],a[cnt]);
	return base;
}

inline void build(int now,int l,int r){
	if(l == r) sgt[now] = make_pair(rk[l],rk[l]), flag[rk[l]] = 1;
	else{
		int mid = (l+r)>>1;
		build(now<<1,l,mid); build(now<<1|1,mid+1,r);
		sgt[now] = pushUp(sgt[now<<1],sgt[now<<1|1]);
	}
}

inline void update(int now,int l,int r,int pos){
	if(l == r){
		flag[rk[l]] ^= 1;
		if(flag[rk[l]]) sgt[now] = make_pair(rk[l],rk[l]);
		else sgt[now] = make_pair(0,0);
		return;
	}
	int mid = (l+r)>>1;
	if(pos <= mid) update(now<<1,l,mid,pos);
	else update(now<<1|1,mid+1,r,pos);
	sgt[now] = pushUp(sgt[now<<1],sgt[now<<1|1]);
}

int main()
{
	scanf("%d",&n); 
	tot = 1; T = (int)(log(n)/log(2))+1;
	rep(i,1,n-1){
		int a,b; scanf("%d%d",&a,&b);
		add(a,b); add(b,a);
	}
	tot = 0; dfs(1,0); build(1,1,n);
	scanf("%d",&m);
	while(m--){
		char op[10]; scanf("%s",op);
		if(op[0] == 'C'){
			int x; scanf("%d",&x); 
			update(1,1,n,dfn[x]);
		}
		else printf("%d
",dist(sgt[1].first,sgt[1].second));
	}
	return 0;
}

  

原文地址:https://www.cnblogs.com/cutemush/p/11831933.html