HXY造公园

Double!

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