【ZJOI2007】捉迷藏(动态树分治)

显然只有一次询问的话,可以用点分治来实现。

但是现在我们有多组询问,还带有修改,我们只能通过动态点分治来做了。

动态点分治的主要思想:省去每次点分治求重心的过程,直接预处理出来(因为树的形态不会改变),建立点分树。那么我们每次分治时只需按照点分树上的路径走就是了。

例如,对于这么一颗树:(样例,1为根)(感谢绘图网站https://csacademy.com/app/graph_editor/

在这里插入图片描述

建出来的点分树是这样的:(3为根)

在这里插入图片描述
注意:点分树只是把在原树中点分治遍历的顺序建了出来,如果求 \(dis\)\(lca\) 还是要在原树中求,不能在点分树上求,因为点分树改变了原树形态。所以如果题目的询问不是针对全局的,而是带有父子关系的(比如多次询问以 \(u\) 为根的子树中路径长度为 \(k\) 的路径个数)就不能建点分树了。

应该不能吧,不知道有没有一种玄学的方法

然后我们对于每个点,维护四个大根堆:

  1. \(dis1\),维护:在点分树中以 \(u\) 为根的子树中,所有灭灯的节点到 \(u\)\(fa\) 的距离。
  2. \(erase1\),因为我们有时要从 \(dis1\) 中删去一些值,所以 \(erase1\) 维护在 \(dis1\) 中要删去的值。
  3. \(dis2\),维护:在点分树中 \(u\) 的所有儿子的 \(dis1\) 的堆顶。那么将 \(dis2\)\(top1\)\(top2\) 取出来,再相加,就是合法的经过 \(u\) 的最长路径的长度。
  4. \(erase2\),和 \(erase1\) 差不多,用来维护在 \(dis2\) 中要删去的值。

由于对于 \(dis1\)\(dis2\) 都有一个删除堆,所以我把 \(dis1\)\(erase1\) 封装在一起,称为 \(heap1\)\(dis2\)\(erase2\)封装在一起,称为 \(heap2\)。那么这两个 \(heap\) 都可以实现 \(top1()\)\(top2()\)\(pop()\)\(erase()\)\(size()\) 操作。

然后我们维护一个全局 \(heap\)\(Ans\) 堆,同样有一个 \(erase\) 堆。\(Ans\)用来维护全局 \(dis2\) 堆的 \(top1\)\(top2\) 之和。

那么如果询问,答案就是 \(Ans\) 堆堆顶。

考虑如果修改一个点,那么只会对它的所有祖先的 \(dis1\)\(dis2\) 有影响。

那么我们记录下询问点 \(Qpoint=u\),然后让 \(u\) 往上跳,更新 \(dis1\)\(dis2\)

代码和注释如下:

#include<bits/stdc++.h>

#define N 100010
#define INF 0x7fffffff

using namespace std;

struct heap
{
	priority_queue<int>q1,q2;
	int size()
	{
		return q1.size()-q2.size();
	}
	void push(int x)
	{
		q1.push(x);
	} 
	void erase(int x)
	{
		q2.push(x);
	}
	void pop()
	{
		while(!q2.empty()&&q1.top()==q2.top())
			q1.pop(),q2.pop();
		q1.pop();
	}
	int top()
	{
		while(!q2.empty()&&q1.top()==q2.top())
			q1.pop(),q2.pop();
		return q1.empty()?0:q1.top();
	}
	int top2()
	{
		if(size()<2)return 0;
		int x=top();
		pop();
		int y=top();
		push(x);
		return y;
	}
}q,q1[N],q2[N];
//q全局路径最大
//q1距离自己父亲距离最大
//q2距离自己距离最大(每个儿子仅有一条路径) 

int n,Q,nn,root,sum;
int cnt,head[N],nxt[N<<1],to[N<<1];
int size[N],maxsize[N];
int d[N],f[N][17];
int fa[N],ans[N];
bool vis[N],open[N];

void adde(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

//------------------------------------------------------------倍增求点之间的距离
void dfs(int u)
{
	for(int i=1;i<=16;i++)
		f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==f[u][0])
			continue;
		f[v][0]=u;
		d[v]=d[u]+1;
		dfs(v);
	}
}

int lca(int a,int b)
{
	if(d[a]<d[b])
		swap(a,b);
	for(int i=16;i>=0;i--)
		if(d[f[a][i]]>=d[b])
			a=f[a][i];
	if(a==b)
		return a;
	for(int i=16;i>=0;i--)
		if(f[a][i]!=f[b][i])
			a=f[a][i],b=f[b][i];
	return f[a][0];
}

int getdis(int a,int b)
{
	return d[a]+d[b]-2*d[lca(a,b)];
}

//------------------------------------------------------------建点分树
void getroot(int u,int fa)
{
	size[u]=1,maxsize[u]=0;
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa||vis[v])
			continue;
		getroot(v,u);
		size[u]+=size[v];
		maxsize[u]=max(maxsize[u],size[v]);
	}
	maxsize[u]=max(maxsize[u],nn-size[u]);
	if(maxsize[u]<maxsize[root])
		root=u;
}

void maketree(int u)
{
	vis[u]=true;
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(vis[v])
			continue;
		nn=size[v],root=0;
		getroot(v,u);
		fa[root]=u;//记录点分树中的fa
		maketree(root);
	}
}

//------------------------------------------------------------修改
void update(int u)
{
	if(!open[u])
	{
		sum++;
		q2[u].push(0);//push(0)是为了保证当灯是关着时,q2[u]的size至少为1
		if(q2[u].size()==2) 
			q.push(q2[u].top());
	}
	else
	{
		sum--;
		if(q2[u].size()==2) 
			q.erase(q2[u].top());
		q2[u].erase(0);
	}
	int now=u;
	while(1)
	{
		int dis=getdis(fa[now],u),t1;
		if(!fa[now])
			return;
		if(!open[u])t1=q1[now].top(),q1[now].push(dis);//更新q1
		else q1[now].erase(dis),t1=q1[now].top();
		if(dis>t1)
		{
			int s1=q2[fa[now]].top()+q2[fa[now]].top2();
			int siz=q2[fa[now]].size();
			if(!open[u])//更新q2
			{
				if(t1) 
					q2[fa[now]].erase(t1);
				q2[fa[now]].push(dis);
			}
			else
			{
				q2[fa[now]].erase(dis);
				if(t1) 
					q2[fa[now]].push(t1);
			}
			int s2=q2[fa[now]].top()+q2[fa[now]].top2();
			if(s2!=s1)//更新Ans堆
			{
				if(siz>=2) 
					q.erase(s1);
				if(q2[fa[now]].size()>=2)
					q.push(s2);
			}
		}
		now=fa[now];
	}
}

//------------------------------------------------------------主程序
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		adde(u,v),adde(v,u);
	}
	d[1]=1;
	dfs(1);
	nn=n,maxsize[0]=INF;
	getroot(1,0);
	maketree(root);
	for(int i=1;i<=n;i++)
		open[i]=true;
	for(int i=1;i<=n;i++)
		update(i),open[i]=false;
	scanf("%d",&Q);
	while(Q--)
	{
		char ch=getchar();
		while(ch!='C'&&ch!='G')
			ch=getchar();
		if(ch=='G')
		{
			if(sum>=2) printf("%d\n",q.top());
			else if(sum==1) puts("0");
			else puts("-1");
		}
		if(ch=='C')
		{
			int u;
			scanf("%d",&u);
			open[u]^=1;
			update(u);
		}
	}
	return 0;
}
posted @ 2022-10-31 07:48  ez_lcw  阅读(27)  评论(0编辑  收藏  举报