[Codeforces Round #260 (Div. 1)] -C Civilization

32 篇文章 0 订阅
22 篇文章 0 订阅
洛谷传送门
Codeforces传送门

题目大意

给你一个有 N N 个点的图。 一开始你已经有M条边连接其中的一些点, 保证连接的点之间不会有环。 现在要求你支持两种操作:

  • 1 A 1   A 询问A所在联通块的直径是多少。
  • 2 A B 2   A   B AB A 、 B 所在联通块之间任意连一条边使之成为一个联通块,并保证操作后联通块的直径最小。如果 AB A 、 B 原本就在一个联通块中则忽略这个操作。

输入输出格式

输入格式

第一行三个整数 N,M,Q N , M , Q ,表示图中共有 N N 个点,原本就有M条边, Q Q 个操作。

2M+1行, 每行两个正整数 ai,bi a i , b i ,表示 ai a i bi b i 间有一条边。

以下 Q Q 行,每行2 3 3 个正整数, 含义如上。

输出格式

对于每个1操作, 输出一个整数表示该点所在联通块的直径。

输入输出样例

输入样例#1
6 0 6
2 1 2
2 3 4
2 5 6
2 3 2
2 5 3
1 1
输出样例#1
4

解题分析

两个联通块合并时,为了保证合并后直径尽量小,我们肯定会在两个联通块的直径的中点之间连一条边。

实际上我们并不需要真的连出这条边,只需要知道当前联通块的大小即可, 有如下公式:

diameter(AB)=max(diameter(A),diameter(B),diameter(A)2+diameter(B)2+1) d i a m e t e r ( A ⋃ B ) = m a x ( d i a m e t e r ( A ) , d i a m e t e r ( B ) , ⌈ d i a m e t e r ( A ) 2 ⌉ + ⌈ d i a m e t e r ( B ) 2 ⌉ + 1 )

所以我们先预处理出每个联通块的直径、点的归属情况,再用 DSU D S U 暴力合并维护即可。

总复杂度 O(N) O ( N )

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <cmath>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define MX 300050
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
int dot, line, q, cnt, root, pos;
int head[MX], dia[MX], bel[MX], dep[MX];
bool vis[MX];
struct Edge {int to, nex;} edge[MX << 1];
IN void addedge(const int &from, const int &to)
{edge[++cnt] = {to, head[from]}, head[from] = cnt;}
namespace DSU
{
    int find(const int &now) {return bel[now] == now ? now : bel[now] = find(bel[now]);}
    IN void combine(R int x, R int y) {bel[x] = find(y);}
}
void DFS(const int &now, const int &fa)
{
    vis[now] = true;
    if(dep[now] > dep[pos]) pos = now;
    for (R int i = head[now]; i; i = edge[i].nex)
    {
        if(edge[i].to == fa) continue;
        dep[edge[i].to] = dep[now] + 1;
        DFS(edge[i].to, now);
    }
}
void merge(const int &now, const int &tar, const int &fa)
{//并查集初始化
    bel[now] = tar;
    for (R int i = head[now]; i; i = edge[i].nex)
    {
        if(edge[i].to == fa) continue;
        merge(edge[i].to, tar, now);
    }
}
void prework(const int &now)
{
    pos = now; DFS(now, 0);//两次DFS求出直径
    root = pos; dep[root] = 0; DFS(root, 0);
    dia[root] = dep[pos]; merge(root, root, 0);
}
int main(void)
{
    int a, b, typ;
    in(dot), in(line), in(q);
    for (R int i = 1; i <= line; ++i)
    in(a), in(b), addedge(a, b), addedge(b, a);
    for (R int i = 1; i <= dot; ++i) if(!vis[i]) prework(i);
    W (q--)
    {
        in(typ);
        if(typ & 1)
        in(a), printf("%d\n", dia[DSU::find(a)]);
        else
        {
            in(a), in(b);
            R int x = DSU::find(a), y = DSU::find(b);
            if(x == y) continue;
            dia[y] = std::max(std::max(dia[y], dia[x]), (dia[y] + 1) / 2 + (dia[x] + 1) / 2 + 1);
            DSU::combine(x, y);
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值