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; }