CF 455C Civilization 题解(直径,并查集)

博客介绍了CF 455C题目,涉及图的直径计算和并查集的智能合并策略。通过选择两个集合直径上的中点进行合并,以优化合并过程。文章提供了样例输入和输出,以及利用直径找图中两点最远距离的算法,证明了这种方法的正确性。

原题链接:
CF
洛谷

题意简述&数据

(交了翻译不知道过没过)
懒得打字了:2333

样例

输入
6 0 6
2 1 2
2 3 4
2 5 6
2 3 2
2 5 3
1 1
输出
4

思路

对于两个集合,如果我们要实现"智能合并"操作,最优的策略肯定是这样的:
选择两个集合的直径(即最长不重复路径)上的中点,合并。设两边的商都分别是 s u , s v su,sv su,sv,且不妨令 s u > = s v su>=sv su>=sv,那么

  1. 合并后的直径长度为 s u su su,因为 s u su su过于长
  2. s u , s v su,sv su,sv相对平衡,导致合并后的长度是 ( s u + 1 ) / 2 + ( s v + 1 ) / 2 + 1 (su+1)/2+(sv+1)/2+1 (su+1)/2+(sv+1)/2+1,即取中点

那么,如何处理初始的边呢?

就相当于求一个图(珂能有环)的直径。类比树的直径求法,我们设计这样一个算法:

  1. 以任意一个点为开始,找到最远的点
  2. 从这个最远的点开始,再找到一个最远的点,就是直径的两个端点

那么,这样对么?显然是对的,证明方法:

意会

好了上代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 300100
    class Graph//图
    {
        public:
            int head[N];
            int EdgeCount;
            struct Edge
            {
                int To,Label,Next;
            }Ed[N<<1];

            void clear()
            {
                memset(Ed,-1,sizeof(Ed));
                memset(head,-1,sizeof(head));
                EdgeCount=0;
            }
            void AddEdge(int u,int v,int w)
            {
                ++EdgeCount;
                Ed[EdgeCount]=(Edge){v,w,head[u]};
                head[u]=EdgeCount;
            }

            int Start(int u)
            {
                return head[u];
            }
            int To(int u)
            {
                return Ed[u].To;
            }
            int Label(int u)
            {
                return Ed[u].Label;
            }
            int Next(int u)
            {
                return Ed[u].Next;
            }
    }G;void Add(int u,int v,int w){G.AddEdge(u,v,w);G.AddEdge(v,u,w);}
    class DSU//并查集
    {
        public:
            int Father[N],Cnt[N];
            void Init()
            {
                for(int i=0;i<N;i++)
                {
                    Father[i]=i;
                    Cnt[i]=1;
                }
            }
            int Find(int x)
            {
                return (x==Father[x])?x:(Father[x]=Find(Father[x]));
            }
    }D;

    int n,m,q;
    void Input()
    {
        scanf("%d%d%d",&n,&m,&q);
        for(int i=1;i<=m;++i)
        {
            int u,v;scanf("%d%d",&u,&v);
            Add(u,v,1);
        }
    }

    int maxlen[N];
    int ans=-1;
    int root;
    int furthest;
    void DFS(int u,int pre,int dis)//找到最远的点
    {
        D.Father[u]=root;
        if (dis>ans)
        {
            ans=dis;
            furthest=u;
        }
        for(int i=G.Start(u);~i;i=G.Next(i))
        {
            int v=G.To(i);
            if (v!=pre)
            {
                DFS(v,u,dis+1);
            }
        }
    }
    void IntelligentMerge(int u,int v)//智能合并
    {
        int au=D.Find(u),av=D.Find(v);
        if (au==av) return;
        int su=maxlen[au],sv=maxlen[av];
        if (su<sv)
        {
            swap(maxlen[au],maxlen[av]);
            swap(su,sv);
        }

        D.Father[av]=au;
        maxlen[au]=max(su,(su+1)/2+(sv+1)/2+1);
    }
    #define IMerge IntelligentMerge//。。。不好意思这个名字真的很长
    void Soviet()//awsl 爱我苏联
    {
        for(int i=1;i<=n;++i)
        {
            if (D.Father[i]==i)
            {
                ans=-1;
                root=furthest=i;
                DFS(root,-1,0);
                ans=-1;
                DFS(furthest,-1,0);
                maxlen[i]=ans;
            }
        }

        for(int i=1;i<=q;++i)
        {
            int o;scanf("%d",&o);
            if (o==2)
            {
                int u,v;scanf("%d%d",&u,&v);
                IMerge(u,v);
            }
            else if (o==1)
            {
                int u;scanf("%d",&u);
                printf("%d\n",maxlen[D.Find(u)]);
            }
        }
    }

    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        D.Init();
        G.clear();
        Input();
        Soviet();
    }
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值