连通

题目描述

给出一张n个点,0条边的图,在第i个时刻(下标从1开始),向图中加入第i条无向边。

有q次询问,每次询问u,v两个点最早在什么时刻连通。

输入

输出

样例输入

4 6
1 3
3 4
1 4
2 4
2 3
1 2
4
1 2
3 4
1 4
2 3

样例输出

4
2
2
4

提示

解题思路

看到题目的第一感觉,发现数据是出奇地大,但这一道题目,肯定是可以构图的。(关键我连构图这一节都分析了好久才想到,而且还放弃并查集用了最短路)。事实上,此题就是需要并查集与LCA,图中的边很多,我们利用并查集其实也可以消去一些没有用的边,最后只可能有N-1条有效边,大大减小了时间复杂度。最后问连通没有,我们仅仅需要求出这一一段树上路径的最短路。

我们处理并查集时,用秩优化,这里特别注意,不要用路径压缩,路径压缩可以说是把树全部打乱了,对我们来说这是致命的,我们用秩优化,因而树就是一棵长度为logn的树。

但树其实是经过并查集处理,是很奇怪的,我们可以找到根节点,再进行dfs,真正构造出一棵树。求到深度,距离。

构造完之后,就很水了,可以暴力求树上最大路径,还可以倍增,速度似乎要快一点。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<vector>
#include<queue>
#include<cstdlib>
#include<algorithm>
using namespace std;
struct edge{
    int u,v;
    edge(){};
    edge(int U,int V){
        u = U,v = V;
    }
};
int n,m,fa[50005],rank1[50005],q,mov[50005],dis[50005];
vector <edge>G[50005];
  
void makeset(int x){
    for (int i = 1;i <= x; i ++){
        fa[i] = i;
        rank1[i] = 1;
    }
}
  
int findset(int x){
    while (x != fa[x])
        x = fa[x];
    return x;
}
  
void unionset(int a,int b,int time){
    int t = findset(a);
    int t1 = findset(b);
    if (t == t1)
        return ;
    G[t].push_back(edge(t1,time));
    G[t1].push_back(edge(t,time));
    if (rank1[t] > rank1[t1])
        fa[t1] = t;
    else {
        fa[t] = t1;
        if (rank1[t] == rank1[t1])
            rank1[t1] ++;
    }
}
  
void dfs(int step){
    int x = G[step].size();
    for (int i = 0;i < x; i ++){
        if (!mov[G[step][i].u]){
            mov[G[step][i].u] = 1;
            rank1[G[step][i].u] = rank1[step] + 1;
            dis[G[step][i].u] = G[step][i].v;
            dfs(G[step][i].u);
        }
    }
}
  
int lca(int u,int v){
    int ans = 0;
    while (rank1[u] > rank1[v]){
        ans = max(dis[u],ans);
        u = fa[u];
    }
    while (rank1[v] > rank1[u]){
        ans = max(dis[v],ans);
        v = fa[v];
    }
    while (u != v){
        ans = max(dis[u],ans);
        u = fa[u];
        ans = max(dis[v],ans);
        v = fa[v];
    }
    return ans;
}
  
int main(){
    scanf ("%d%d",&n,&m);
    makeset(n);
    for (int i = 1 ;i <= m;i ++){
        int a,b;
        scanf ("%d%d",&a,&b);
        unionset(a,b,i);
    }
    for (int i = 1;i <= n;i ++){
        if (fa[i] == i){
            mov[i] = 1;
            rank1[i] = 1;
            dfs(i);
        }
    }
    scanf ("%d",&q);
    while (q --){
        int a,b;
        scanf ("%d%d",&a,&b);
        if (findset(a) != findset(b))
            printf("-1\n");
        else if (a == b)
            printf("0\n");
        else  
            printf("%d\n",lca(a,b));
    }
}

总结

我表示这道题考试时候是摸到了一点思路的,但确实是没想到树,构造这一棵树才是最为关键的。树构造出来,之后的答案就可以很好很轻松地做了。

猜你喜欢

转载自blog.csdn.net/weixin_43904950/article/details/87971818
今日推荐