• 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
    ",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;
    }
    

     

  • 相关阅读:
    (C)const关键字
    (C)volatile关键字
    蛋疼的四旋翼
    多线程之:死锁
    多线程之:ThreadLocal
    多线程之:线程同步代码块
    多线程之:线程安全
    多线程之:竞态条件&临界区
    多线程之:java线程创建
    多线程之:多线程的优缺点
  • 原文地址:https://www.cnblogs.com/ninedream/p/13769638.html
Copyright © 2020-2023  润新知