修路(HGOI)

修路 (build.pas/c/cpp)

问题描述

有 n 个兵营里驻扎着士兵,由于兵营间没有路,士兵不能相互增援。现在工程队每天
可以修建一条连接两个兵营的双向道路。现在指挥官询问,对于一个兵营 u,最早什么时候
可以增援兵营 v,即两个兵营之间有连通的路径。

输入数据

第一行两个整数 N,M,表示兵营的数量和道路的数量,兵营从 1-N 编号。
接下来 m 行,每行两个整数 x,y,表示会按顺序修建兵营 x 和兵营 y 之间的道路,所有道
路都是双向的,数据可能存在重边。
接下来一行一个整数 Q,表示有 Q 个询问。
接下来 Q 行每行两个整数 u,v,表示询问兵营 u 最早在什么时候能出兵增援兵营 v。

输出数据

每行一个整数 ti,表示对于第 i 个询问,最早在 ti 天后能满足要求。若始终无法满足,
则输出-1。

输入样例 1

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

输出样例 1

1
2
-1

数据范围

对于 30%的数据,1 N 100,1 M 1000,1 Q 1000。
对于 100%的数据,1 N 10^5,1 M 10^6,1 Q 10^5。

题解

  • 刚拿到这一道题有点手足无措,第一点想到的是要求天数最短并且有重边,那么可以确定是最小生成树,并且是Kruskal算法。
  • 接着想了一个多小时,想出来查询的两个点必定经过他们的公共祖先,那么只需要在倍增算法搜索到公共祖先的同时找到最大的所经过边的权值,可以用dp来做。
  • 剩下的就是代码实现问题了(然而我很可怜的调一个越界挑了一个下午TAT)

附上代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define MAXN 100010
using namespace std;
void ff(){
    freopen("build.in","r",stdin);
    freopen("build.out","w",stdout);
}
struct Edge{
    int from,to,v;
};
vector <Edge>ss;
vector <int> G[MAXN];
int n,m,Q,large;
//并查集
int father[MAXN],rank1[MAXN];
int find(int x){
    if(x!=father[x]){
        father[x]=find(father[x]);
    }
    return father[x];
}
void merge(int x,int y){
    x=find(x),y=find(y);
    if(rank1[x]<rank1[y]){
        father[x]=father[y];
    }else{
        father[y]=father[x];
        if(rank1[x]==rank1[y]){
            rank1[x]++;
        }
    }
}

//搜索建树同时LCA
bool visited[MAXN];
int d[MAXN],p[MAXN][20],tt[MAXN][20];
void dfs(int u,int deepth){
    d[u]=deepth;visited[u]=true;
    int size=G[u].size();
    for (int i=0;i<size;i++){
        int v=ss[G[u][i]].to;
        if(!d[v]){
            p[v][0]=u;
            tt[v][0]=ss[G[u][i]].v;
            dfs(v,deepth+1);
        }
    }
}
void make(){
    for (int j=1;(1<<j)<=n;j++){
        for (int i=1;i<=n;i++){
            p[i][j]=p[p[i][j-1]][j-1];
            tt[i][j]=max(tt[i][j-1],tt[p[i][j-1]][j-1]);//关键存最大边的方程
        }

    }
}
int lca(int x,int y){//LCA搜索
    int ans1=0,ans2=0;
    if(d[x]<d[y]){
        int temp=x;
        x=y;
        y=temp; 
    }
    for (int i=large;i>=0;i--){
        if(d[y]<=d[x]-(1<<i)){
            ans1=max(ans1,tt[x][i]);
            x=p[x][i];
        }
    }
    if(x==y) return ans1;

    for (int i=large;i>=0;i--){
        if(p[x][i]!=p[y][i]){
            ans1=max(ans1,tt[x][i]);
            ans2=max(ans2,tt[y][i]);
            x=p[x][i];
            y=p[y][i];
        }
    }
    ans1=max(ans1,tt[x][0]);
    ans2=max(ans2,tt[y][0]);
    return max(ans1,ans2);
}

int main(){
    ff();
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) father[i]=i;

    for (int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        int x=find(a),y=find(b);
        if(x!=y){//读入同时进行Kruskal的建树
            merge(x,y);
            ss.push_back((Edge){a,b,i});
            G[a].push_back(ss.size()-1);
            ss.push_back((Edge){b,a,i});
            G[b].push_back(ss.size()-1);
        }
    }

    memset(visited,false,sizeof(visited));
    memset(tt,0,sizeof(tt));
    for (int i=1;i<=n;i++){
        if(!visited[i]){
            p[i][0]=i;
            dfs(i,1);
        }
    }
    large=0;
    while(1<<large<=n) large++;
    large--;
    make();

    scanf("%d",&Q);
    for (int i=1;i<=Q;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        if(find(a)!=find(b)){
            printf("-1\n");
            continue;
        }
        printf("%d\n",lca(a,b));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值