最大生成树+LCA倍增

描述

 

A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重
量限制,简称限重。现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的
情况下,最多能运多重的货物。
 

输入格式

输入文件第一行有两个用一个空格隔开的整数 n, m, 表示 A 国有 n 座城市和 m 条道
路。
接下来 m 行每行 3 个整数 x、 y、 z,每两个整数之间用一个空格隔开,表示从 x 号城市
到 y 号城市有一条限重为 z 的道路。注意: x 不等于 y,两座城市之间可能有多条道路。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、 y,之间用一个空格隔开,表示一辆货车需要从 x 城市
运输货物到 y 城市,注意: x 不等于 y。

输出格式

输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货
车不能到达目的地,输出-1。

测试样例1

输入

4 3 
1 2 4 
2 3 3 
3 1 1 

1 3 
1 4 
1 3

输出

3- 

3

备注

对于 30%的数据, 0 < n< 1,000, 0 < m< 10,000, 0 < q< 1,000;
对于 60%的数据, 0 < n< 1,000, 0 < m< 50,000, 0 < q< 1,000;

对于 100%的数据, 0 <n < 10,000, 0 <m < 50,000, 0 <q < 30,000,

0 ≤ z ≤ 100,000。

by 460289052

题解来自未知大牛:

最根本算法就是最大生成树+倍增
 倍增很好理解 就是我想要让一个树上的点到达他祖先中的某点
 而且每次拜访到他父亲是都会有一个对应的权值
 那么我就预先处理两个数组:
 数组f[i][j] 存储结点i的第2^j的前辈对应的数字
 数组g[i][j] 存储结点i到第2^j的前辈对应的最小权值

 可以经过繁杂的推导 证明 f[i][j]=f[f[i][j-1]][j-1]
 即Pi的2^j-1再2^j-1就可以到达其的 2^j
 再根据这个 推导出 g[i][j]=min(g[i][j-1],g[f[i][j-1]][j-1])
 即将从Pi到2次j次方祖先分为两段 
 第一段是从Pi到2^j-1祖先里最小的权值
 第二段是从Pi的2^j-1祖先到Pi的2^j祖先中间最小的权值
 从中取最小值就是对应的从Pi到它的2^j祖先中间最小的权值
 这个g函数的思路就是动态规划

 现在处理好了f,g两个数组
 而用2作为次幂的原因是可以用2的次幂相加表示出所有的正整数
 所以可以相应地缩短处理 两个结点回推到最近共同祖先的这一个过程 

 最大生成树在此不论
 (其中有用到并查集判断是否两个结点是否处于同一棵树下,用kruscal算法处理)

我只是负责写代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

struct edge{
    int to,w,next;
}p[20010];
int head[10010];
int tot;
void addedge(int a,int b,int w){
    p[tot].to=b;
    p[tot].w=w;
    p[tot].next=head[a];
    head[a]=tot++;
}
struct line{
    int u,v,w;
    line(){}
    line(int uu,int vv,int ww):u(uu),v(vv),w(ww){}
    bool operator<(const line &a)const{
        return w>a.w;
    }
}q[50010];
int fa[10010];
int findset(int x){
    return fa[x]==x?x:(fa[x]=findset(fa[x]));
}
void unionset(int a,int b){
    fa[findset(a)]=findset(b);
}
int f[10010][20];
int d[10010][20];
int dep[10010];
int vis[10010];
void dfs(int u,int fa){
    f[u][0]=fa;
    dep[u]=dep[fa]+1;
    vis[u]=1;
    for(int i=head[u];i!=-1;i=p[i].next){
        int v=p[i].to;
        if(v!=fa){
            d[v][0]=p[i].w;
            dfs(v,u);
        }
    }
}
void init(int n){
    memset(vis,0,sizeof vis);
    memset(d,0x3f,sizeof d);
    for(int i=1;i<=n;i++)
        if(!vis[i]) {dep[i]=0;dfs(i,0);}
    for(int j=0;j<18;j++)
        for(int i=1;i<=n;i++)
            if(!f[i][j]) {f[i][j+1]=0;d[i][j+1]=0;}
            else{
                f[i][j+1]=f[f[i][j]][j];
                d[i][j+1]=min(d[i][j],d[f[i][j]][j]);
            }
}
int LCA(int u,int v){
    int ans=0x3f3f3f3f;
    if(dep[u]<dep[v]) swap(u,v);
    int df=dep[u]-dep[v],t=0;
    while(df){
        if(df&1){
            ans=min(ans,d[u][t]);
            u=f[u][t];
        }
        t++;
        df>>=1;
    }
    if(u==v) return ans;
    for(int i=18;i>=0;i--)
        if(f[u][i]!=f[v][i]){
            ans=min(ans,d[u][i]);
            ans=min(ans,d[v][i]);
            u=f[u][i];
            v=f[v][i];
        }
    return min(ans,min(d[u][0],d[v][0]));
}
int main()
{
    int n,m,a,b,c;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&a,&b,&c);
        q[i]=line(a,b,c);
    }
    sort(q,q+m);
    for(int i=1;i<=n;i++) fa[i]=i;
    memset(head,-1,sizeof head);
    tot=0;
    int cnt=0;
    for(int i=0;i<m;i++){ //kruskal
        int u=q[i].u,v=q[i].v;
        if(findset(u)==findset(v)) continue;
        unionset(u,v);
        addedge(u,v,q[i].w);
        addedge(v,u,q[i].w);
        if(++cnt==n-1) break;
    }
    init(n);
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&a,&b);
        if(findset(a)!=findset(b)) puts("-1");
        else printf("%d\n",LCA(a,b));
    }
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值