【洛谷P2056】捉迷藏

题目

题目链接:https://www.luogu.com.cn/problem/P2056
Jiajia 和 Wind 是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind 和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由 \(N\) 个屋子和 \(N-1\) 条双向走廊组成,这 \(N-1\) 条走廊的分布使得任意两个屋子都互相可达。
游戏是这样进行的,孩子们负责躲藏,Jiajia 负责找,而 Wind 负责操纵这 \(N\) 个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia 希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。
我们将以如下形式定义每一种操作:

  • C(hange) i 改变第 \(i\) 个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。
  • G(ame) 开始一次游戏,查询最远的两个关灯房间的距离。

\(n\leq 10^5,m\leq 5\times 10^5\)

思路

建出点分树,对于点分树上每一个点,维护两个堆 \(q1,q2\)。其中 \(q1\) 放点分树上在这个点 \(x\) 为根的子树中,所有点到点分树上 \(x\) 父亲的距离。\(q2\) 放点 \(x\) 所有子树的堆的最大值。
那么显然答案就是每一个点的 \(q2\) 的最大值和次大值之和的最大值。所以我们再开一个堆存所有点答案的最大值。
对于一次修改操作,我们从 \(x\) 往上跳父亲,更新它与父亲的堆即可。
对于查询操作,就直接输出答案堆的堆顶。注意只有一个关灯房间和没有关灯房间需要特判。
但是普通的堆是不支持删除的,所以我们再开三个堆分别对应前文三个堆,如果要在某一个堆中删除,那么就在其对应的堆中插入,需要时不断判断两个堆顶是不是相同即可。
时间复杂度 \(O(m\log^2 n)\)

代码

常数不是很优秀,开了 O2 才过。

#include <bits/stdc++.h>
using namespace std;

const int N=100010,LG=18,Inf=1e9;
int n,m,rt,tot,cnt,head[N],fa[N],dep[N],maxp[N],size[N],f[N][LG+1];
bool vis[N];
char ch[3];
priority_queue<int> q1[N],q2[N],d1[N],d2[N],qans,dans;

struct edge
{
	int next,to;
}e[N*2];

void add(int from,int to)
{
	e[++tot]=(edge){head[from],to};
	head[from]=tot;
}

void dfs1(int x,int _fa)
{
	f[x][0]=_fa; dep[x]=dep[_fa]+1;
	for (int i=1;i<=LG;i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=_fa) dfs1(v,x);
	}
}

void findrt(int x,int _fa,int sum)
{
	size[x]=1; maxp[x]=0;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (!vis[v] && v!=_fa)
		{
			findrt(v,x,sum);
			size[x]+=size[v];
			if (size[v]>maxp[x]) maxp[x]=size[v];
		}
	}
	if (sum-size[x]>maxp[x]) maxp[x]=sum-size[x];
	if (maxp[x]<maxp[rt]) rt=x;
}

void dfs2(int x,int _fa,int sum)
{
	vis[x]=1; fa[x]=_fa;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (!vis[v])
		{
			int s=(size[v]<size[x])?size[v]:sum-size[x];
			rt=0;
			findrt(v,0,s); dfs2(rt,x,s);
		}
	}
}

int lca(int x,int y)
{
	if (dep[x]<dep[y]) swap(x,y);
	for (int i=LG;i>=0;i--)
		if (dep[f[x][i]]>=dep[y]) x=f[x][i];
	if (x==y) return x;
	for (int i=LG;i>=0;i--)
		if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
} 

void updans(int x,bool flag)
{
	if (!q2[x].size()) return;
	int tmp=q2[x].top(); q2[x].pop();
	while (q2[x].size() && d2[x].size() && q2[x].top()==d2[x].top())
		q2[x].pop(),d2[x].pop();
	if (!flag && q2[x].size()) qans.push(q2[x].top()+tmp);
	if (flag && q2[x].size()) dans.push(q2[x].top()+tmp);
	q2[x].push(tmp);
}

void update(int x,bool flag)
{
	updans(x,1);
	if (flag) d2[x].push(0);
		else q2[x].push(0);
	while (q2[x].size() && d2[x].size() && q2[x].top()==d2[x].top())
		q2[x].pop(),d2[x].pop();
	updans(x,0);
	for (int i=x;fa[i];i=fa[i])
	{
		int dis=dep[x]+dep[fa[i]]-2*dep[lca(x,fa[i])];
		updans(fa[i],1);
		if (q1[i].size()) d2[fa[i]].push(q1[i].top());
		if (flag) d1[i].push(dis);
			else q1[i].push(dis);
		while (q1[i].size() && d1[i].size() && q1[i].top()==d1[i].top())
			q1[i].pop(),d1[i].pop();
		if (q1[i].size()) q2[fa[i]].push(q1[i].top());
		while (q2[fa[i]].size() && d2[fa[i]].size() && q2[fa[i]].top()==d2[fa[i]].top())
			q2[fa[i]].pop(),d2[fa[i]].pop();
		updans(fa[i],0);
	}
	while (qans.size() && dans.size() && qans.top()==dans.top())
		qans.pop(),dans.pop();
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d",&n);
	for (int i=1,x,y;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y); add(y,x);
	}
	rt=0; maxp[0]=Inf;
	dfs1(1,0);
	findrt(1,0,n); dfs2(rt,0,n);
	memset(vis,0,sizeof(vis));
	for (int i=1;i<=n;i++)
		update(i,0);
	cnt=n;
	scanf("%d",&m);
	while (m--)
	{
		scanf("%s",ch);
		if (ch[0]=='G')
		{
			if (!cnt) printf("-1\n");
			else if (cnt==1) printf("0\n");
			else printf("%d\n",qans.top());
		}
		else
		{
			int x;
			scanf("%d",&x);
			vis[x]^=1;
			update(x,vis[x]);
			if (vis[x]==1) cnt--;
				else cnt++;
		}
	}
	return 0;
}
posted @ 2021-01-04 10:33  stoorz  阅读(177)  评论(0编辑  收藏  举报