Description
要求完成两种操作:
- 查询一个点所在树的直径;
- 添加一条边合并两个点所在的树,要求合并后新树的直径最小。
Solution
显然我们无法每次真的合并两棵树再重新跑一遍直径,那我们看一下树的合并有什么特殊的性质。
通过手玩几个简单的样例我们可以发现,直径为 \(x\) 的树与直径为 \(y\) 的树合并后形成的新树直径为 \(\max\{x,y,\lceil \frac{x}{2} \rceil+\lceil \frac{y}{2}\rceil+1\}\)。
因为要保证新树直径最小,那我们肯定会连接两棵树的直径的中点。
若两棵树的直径 \(x>y\),那我们连接后可以发现,新树的直径大小仍然是 \(x\)。\(x<y\) 时同理为 \(y\)。
若两棵树的直径 \(x=y\),那我们连接后可以发现,新树的直径大小为
\(\lceil \frac{x}{2} \rceil+\lceil \frac{y}{2}\rceil+1\)。
因为在长度不同时前者大于后者,长度相同时后者大于前者。所以三者直接去最大值即可。
我们先求出初始的森林中每棵树的直径,合并与判断操作靠并查集实现,然后更新所在树的直径即可。
int n,m,q,tot,len,sec;
int head[maxn],fa[maxn];
int ans[maxn],Dis[maxn];
struct edge{int fr,to,nxt;}e[maxn<<1];
void add(int fr,int to){
e[++tot]=(edge){fr,to,head[fr]};
head[fr]=tot;
}
int findf(int x){
return fa[x]^x?fa[x]=findf(fa[x]):x;
}
void dfs(int u,int fat){
Dis[u]=Dis[fat]+1;
if(Dis[u]>len)len=Dis[u],sec=u;
for(int i=head[u];i;i=e[i].nxt){
int to=e[i].to;
if(to==fat) continue;
dfs(to,u);
}
}
int main(){
n=read();m=read();q=read();
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1,fr,to,dis;i<=m;i++){
fr=read();to=read();
add(fr,to);add(to,fr);
fr=findf(fr),to=findf(to);
if(fr!=to) fa[to]=fr;
}
for(int i=1;i<=n;i++){
if(findf(i)!=i) continue;
dfs(i,0);len=0;dfs(sec,0);
ans[i]=len-1;len=0;sec=0;
}
for(int i=1,opt,x,y;i<=q;i++){
opt=read();x=read();
if(opt==1) printf("%d\n",ans[findf(x)]);
else{
y=read();x=findf(x);y=findf(y);if(x==y) continue;fa[y]=x;
ans[x]=max(max(ans[x],ans[y]),(ans[x]+1)/2+(ans[y]+1)/2+1);
}
}
return 0;
}