Codeforces 455C. Civilization (树的直径+并查集)

题目描述

传送门

题目大意:
(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];
            }
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值