CF455C Civilization

题意简述

题目链接

  给定一n个点、m条边的森林,q次操作,操作分两种:1.给定一个点x,要求x所在的树的直径;2.给定两个点x,y,选取x所在树中的一个点u,y所在树中的一个点v,新增一条边(u,v),合并两棵树,使得合并后的新树的直径最小。

算法概述

  对于初始的森林,显然可以dp一遍求出所有树的原始直径。

  先给所有无根树定根,可以用并查集维护连通性,然后以祖先节点为根。

  以len[i]表示以i为根的这棵树的直径,则操作一只需并查集找到祖先节点,然后输出该点的len即可。

  主要看操作二。

  合并两棵树,连通性依然能够用并查集继续维护,关键是合并后新树的直径如何得到。

  首先,设合并前x所在树的直径为d1,y所在树的直径为d2,则新树的直径d必然满足d>=max(d1,d2),否则max(d1,d2)为直径,矛盾!

  设我们新加的边为(u,v),于是我们可以把新树的所有可能成为直径的路径分为两类:

  1.原两棵树内的路径,最长即为原两棵树的直径,即d1,d2。

  2.经过(u,v)的路径。

  第一类已求,故我们只需求出第二类即可。

  由于我们要使新树的直径最小,所以对于加边之前u所在的树,我们所取的u需要满足树中距离u最远的点与u的距离最小(意即:设dis[i]为i所在树中距离i最远的点与i的距离,则对于树中除u外所有点i,需要满足dis[u]<=dis[i],即u的dis值为最小)。

  那么不难发现一个结论:u必然在其所在树的直径上。反证:若u不在其所在树的直径上,设直径上距离u最近的点为p,设u与p的距离为x,点p将直径分为两部分,设两部分的长度分别为l1,l2。则根据定义,dis[u]=x+max(l1,l2),而dis[p]=max(l1,l2)<=dis[u],则u不是dis值最小的点,矛盾!

  所以,u在其所在树的直径上,那么u便会将直径分为两部分l1,l2,因为我们要加入路径中的长度是max(l1,l2),所以我们要使max(l1,l2)尽量小,那么只要使得u尽量靠近直径的中点即可,即d1/2上取整,算术表达式即为(d1+1)/2。

  那么u的部分我们就求完了,v的部分同理即可。

  所以最后第二类的答案即为(d1+1)/2+(d2+1)/2+1。

  最后与第一类答案取一个max然后更新树的直径即可。

  时间复杂度为O((n+m+q)logn)

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=3e5+10,M=3e5+10;
struct Edge{
	int to,next;
}edge[M<<1];int idx;
int h[N];

void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}

int len[N],vis[N];
int d[N],f[N];
int fa[N];

int n,m,q;

void init()
{
	memset(h,-1,sizeof h);
	for(int i=1;i<=n;i++)fa[i]=i;
}

int get(int x)
{
	if(fa[x]==x)return x;
	return fa[x]=get(fa[x]);
}

void dp(int p)
{
	vis[p]=1;
	for(int i=h[p];~i;i=edge[i].next)
	{
		int to=edge[i].to;
		if(vis[to])continue;
		dp(to);
		f[p]=max(f[p],d[p]+d[to]+1);
		d[p]=max(d[p],d[to]+1);
	}
}

int main()
{
	scanf("%d%d%d",&n,&m,&q);
	init();
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		add_edge(x,y);
		add_edge(y,x);
		x=get(x),y=get(y);
		fa[x]=y;
	}
	
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])dp(get(i));
		int u=get(i);
		len[u]=max(len[u],f[i]);
	}
	
	while(q--)
	{
		int tp,x,y;
		scanf("%d%d",&tp,&x);
		if(tp==1)
		{
			x=get(x);
			printf("%d\n",len[x]);
			continue;
		}
		scanf("%d",&y);
		x=get(x),y=get(y);
		if(x==y)continue;
		int d1=len[x],d2=len[y];
		len[y]=max(d1,d2);
		len[y]=max(len[y],((d1+1)>>1)+((d2+1)>>1)+1);
		fa[x]=y;
	}

	return 0;
}

 

posted @ 2020-10-05 12:27  魑吻丶殇之玖梦  阅读(265)  评论(0编辑  收藏  举报