题目描述
传送门
题目大意:
(1)询问x所在连通块的最长链
(2)将x,y所在的连通块合并,任意两个点之间可以连边,使合并后连通块的最长链最短。
题解
比较容易想到的是连接的两个点是两棵树的中心(最长链的中点)。
关键是连接后的树中点是否会改变呢?答案是连接后的中心是两棵树中最长链较长的树的中心。
那么我们把一个连通块的代表元素维护成树的中心,在这个点动态维护经过这个点子树中的最长链和次长链。
那么对于询问,就是找到代表元素求最长链和次长链的和。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 600003
using namespace std;
int tot,head,p[N],n,m,q,top,st[N],ans,ansx;
int point[N],v[N],nxt[N],pre[N],fa[N],pd[N],val[N],f[N],g[N];
void add(int x,int y)
{
tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void dfs(int x,int f,int d)
{
st[++top]=x; pd[x]=1;
if (d>ans) {
ans=d; ansx=x;
}
for (int i=point[x];i!=-1;i=nxt[i]){
if (v[i]==f) continue;
pre[v[i]]=i;
dfs(v[i],x,d+1);
}
}
int getroad(int x,int y)
{
head=0;
while (x!=y) {
p[++head]=x;
x=v[pre[x]^1];
}
p[++head]=x;
return p[(head+1)/2];
}
int find(int x)
{
if (fa[x]==x) return x;
fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
freopen("a.in","r",stdin);
freopen("my.out","w",stdout);
scanf("%d%d%d",&n,&m,&q);
tot=-1;
memset(point,-1,sizeof(point));
for (int i=1;i<=m;i++) {
int x,y; scanf("%d%d",&x,&y);
add(x,y);
}
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=n;i++)
if (!pd[i]) {
ansx=ans=-1;
dfs(i,0,0);
int t=ansx; ansx=ans=-1; top=0;
dfs(t,0,0);
int mid=getroad(ansx,t);
val[mid]=ans; f[mid]=ans/2; g[mid]=ans-f[mid];
if (f[mid]<g[mid]) swap(f[mid],g[mid]);
for (int j=1;j<=top;j++) fa[st[j]]=mid;
}
for (int i=1;i<=q;i++) {
int opt,x,y; scanf("%d%d",&opt,&x);
if (opt==1) {
x=find(x);
printf("%d\n",val[x]);
}
else {
scanf("%d",&y);
int r1=find(x); int r2=find(y);
if (r1==r2) continue;
if (val[r1]>val[r2]) {
fa[r2]=r1;
if (f[r2]+1>f[r1]) g[r1]=f[r1],f[r1]=f[r2]+1;
else g[r1]=max(f[r2]+1,g[r1]);
val[r1]=g[r1]+f[r1];
}
else {
fa[r1]=r2;
if(f[r1]+1>f[r2]) g[r2]=f[r2],f[r2]=f[r1]+1;
else g[r2]=max(g[r2],f[r1]+1);
val[r2]=f[r2]+g[r2];
}
}
}
}