【洛谷2245】星际导航

题目描述

原题通道
sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好。为了方便起见,我们可以认为宇宙是一张有N 个顶点和M 条边的带权无向图,顶点表示各个星系,两个星系之间有边就表示两个星系之间可以直航,而边权则是航行的危险程度。

sideman 现在想把危险程度降到最小,具体地来说,就是对于若干个询问(A, B),sideman 想知道从顶点A 航行到顶点B 所经过的最危险的边的危险程度值最小可能是多少。作为sideman 的同学,你们要帮助sideman 返回家园,兼享受安全美妙的宇宙航行。所以这个任务就交给你了。

输入输出格式

输入格式:

第一行包含两个正整数N 和M,表示点数和边数。

之后 M 行,每行三个整数A,B 和L,表示顶点A 和B 之间有一条边长为L 的边。顶点从1 开始标号。

下面一行包含一个正整数 Q,表示询问的数目。

之后 Q 行,每行两个整数A 和B,表示询问A 和B 之间最危险的边危险程度的可能最小值。

输出格式:

对于每个询问, 在单独的一行内输出结果。如果两个顶点之间不可达, 输出impossible。

输入输出样例

输入样例#1

4 5
1 2 5
1 3 2
2 3 11
2 4 6
3 4 4
3
2 3
1 4
1 2

输出样例1:

5
4
5

说明

对于40% 的数据,满足N≤1000,M≤3000,Q≤1000。

对于 80% 的数据,满足N≤10000,M≤105,Q≤1000。

对于 100% 的数据,满足N≤105,M≤3×105,Q≤105,L≤109。数据不保证没有重边和自环。

题解:

这题其实就是 NOIP2013 的翻版,标签是倍增,但是我不想拘泥于倍增其实是不熟练就打了树剖。
可能因为这题有多个联通块,用树剖做起来比较麻烦(建n颗线段树)所以用树剖的人很少,但是这个麻烦还是可以解决的:
只要建一个虚点(这里使用0号节点),将所有联通块连起来即可,再从0号节点dfs就只用dfs一遍了,然后注意一些细节(在代码中指出),这题就基本上没什么问题了!

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#define RG register
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;

inline int gi(){
    int data=0,w=1;
    char ch=0;
    while(ch!='-'&&(ch>'9'||ch<'0')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') data=data*10+ch-'0',ch=getchar();
    return data*w;
}
#define N 100010
int n,m,fa[N],p[N],a[N];
struct Edge{int u,v,w;}e[N*3];//存最小生成树的边

struct Node{int to,cost;};
vector<Node> G[N];//本人习惯用vector存图
inline void Add_Edge(int u,int v,int w){
    G[u].push_back((Node){v,w});
    G[v].push_back((Node){u,w});
}

//并差集判是否在一个集合+最小生成树
int findSet(int x){return p[x]==x?x:p[x]=findSet(p[x]);}
inline void Unite(int u,int v){p[findSet(u)]=p[findSet(v)];}
inline bool same(int u,int v){return findSet(u)==findSet(v);}
inline bool cmp(const Edge &a,const Edge &b){return a.w<b.w;}
inline void kruskal(){
    sort(&e[1],&e[m+1],cmp);
    for(RG int i=1;i<=m;i++){
        if(same(e[i].v,e[i].u)) continue;
        Add_Edge(e[i].v,e[i].u,e[i].w);
        Unite(e[i].v,e[i].u);
    }
}
//以下是树链剖分
int top[N],dep[N],size[N],son[N],L[N],dfn[N],tim=-1;//细节1:tim赋为-1(因为从0开始dfs)
#define cur G[x][i]
void dfs(int x,int f){//把边权化为点权并求出fa[x]
    fa[x]=f;
    for(RG int i=0;i<G[x].size();i++){
        if(cur.to==fa[x]) continue;
        a[cur.to]=cur.cost;
        dfs(cur.to,x);
    }
}
void dfs1(int x){
    dep[x]=dep[fa[x]]+1;
    size[x]=1;
    son[x]=n+1;//细节2:重儿子是赋为n+1(0是根节点)
    for(RG int i=0;i<G[x].size();i++){
        if(cur.to==fa[x]) continue;
        dfs1(cur.to);
        size[x]+=size[cur.to];
        if(size[son[x]]<size[cur.to]) son[x]=cur.to;
    }
}
void dfs2(int x,int tp){
    top[x]=tp;
    tim++;
    dfn[tim]=x;L[x]=tim;
    if(son[x]!=n+1) dfs2(son[x],tp);
    for(RG int i=0;i<G[x].size();i++){
        if(cur.to==fa[x]||cur.to==son[x]) continue;
             dfs2(cur.to,cur.to);
    }
}
//线段树维护路径最小
class SegMent_Tree{
    #define lson ( o<<1)
    #define rson ((o<<1)|1)
    public:
    int val[N<<2];
    void build(int o,int l,int r){
        if(l==r){
            val[o]=a[dfn[l]];
            return ;
        }
        int mid=(l+r)>>1;
        build(lson,l,mid);
        build(rson,mid+1,r);
        pushup(o);
    }
    int query(int o,int ql,int qr,int l,int r){
        if(l>qr||r<ql) return -99999999;
        if(ql<=l&&r<=qr) return val[o];
        int mid=(l+r)>>1;
        return max(query(lson,ql,qr,l,mid),query(rson,ql,qr,mid+1,r));
    }
    inline int Ask(int u,int v){
        int ret=-99999999;
        while(top[u]!=top[v]){
            if(dep[top[u]]<dep[top[v]]) swap(u,v);
            ret=max(ret,query(1,L[top[u]],L[u],1,n));
            u=fa[top[u]];
        }
        if(dep[u]<dep[v]) swap(u,v);
        return max(ret,query(1,L[v]+1,L[u],1,n));//细节3:注意是L[v]+1,自己想一想是什么原因
    }
    private:
    inline void pushup(int o){val[o]=max(val[lson],val[rson]);}
}t;

int main(){
    n=gi();m=gi();
    for(RG int i=1;i<=m;i++) e[i].u=gi(),e[i].v=gi(),e[i].w=gi();
    for(RG int i=1;i<=n;i++) p[i]=i;//初始化并查集
    kruskal();//生成树
    memset(fa,-1,sizeof(fa));//把fa数组初始化成-1,因为建的虚点为0
    fa[0]=0;//细节4:fa[0]=0,要不然会RE
    for(RG int i=1;i<=n;i++) if(fa[i]==-1) dfs(i,0);
    for(RG int i=1;i<=n;i++) if(fa[i]==0) Add_Edge(i,0,0);
    dfs1(0);dfs2(0,0);
    RG int T=gi();
    t.build(1,1,n);
    while(T--){
        int u=gi(),v=gi();
        if(!same(u,v)) puts("impossible");
        else printf("%d\n",t.Ask(u,v));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值