C++题解:[NOIP2013] 货车运输

该博客介绍了一种使用C++解决NOIP2013货车运输问题的方法,涉及最大生成树、最近公共祖先等算法。通过Kruskal构造最大生成树,并利用ST算法求解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。

数据范围

0<n<10,000,0<m<50,000,0<q<30,000,0≤z≤100,000。

输出时每行末尾的多余空格,不影响答案正确性

要求使用「文件输入输出」的方式解题,输入文件为 truck.in,输出文件为 truck.out

样例输入

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

样例输出

3
-1
3

题解:

知识点:链式前向星、并查集、最大生成树、深度优先搜索、动态规划、最近公共祖先

  

分析:

我们首先将问题简单化,考虑只有一次询问,可以把边从大到小排序然后逐渐加进来,直到 u,v 连通(读者可以画画图,肯容易得到) 

那么多次询问每次我们就都可以重复这样的步骤,我们发现如果一条边加进来的时候,它的两个端点早就连通了,那这条边就是没有意义的

所以有意义的边恰好构成了一颗最大生成树(MST)(读者可以画画图,肯容易得到)

那么对于每次询问我们只需要在最大生成树上求出路径上的最小边就可以了。这个就可以用倍增(ST算法)法求LCA来做

对于每一次询问输出答案时就可以在求 LCA 的过程中得到 u 到 lca(u,v)(表示u、v的最近公共祖先) 的限重,v 到 lca(u,v) 的限重,取较小者即可

我们先用Kruskal做MST,再用ST做LCA,LCA见计蒜客lca批注-CSDN博客 

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define _for(i,a,b) for (int i=a;i<=b;i++)
using namespace std;
const int N=1e4+5,M=5e4+5;//g[u][j]表示u到f[u][j]这条链上边权的最大值。
int n,m,fa[N],h[N],e[M],ne[M],w[M],idx,d[N],f[N][20],g[N][20];
struct node{
    int a,b,w;
    bool operator <(const node& rhs) const{
        return w>rhs.w;
    }
}a[M];
inline void c_plus(){//可以忽略
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
}
inline void init_list(){//单链表
    idx=0;
    memset(h,-1,sizeof(h));
}
inline void add(int a,int b,int c){//同上
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
inline void init_set(){//并查集
    _for(i,1,n){
        fa[i]=i;
    }
}
int get(int x){//同上
    if (x==fa[x]){
        return x;
    }
    return fa[x]=get(fa[x]);
}
void kruskal(){//kruskal算法
    sort(a+1,a+m+1);
    _for(i,1,m){
        int x=get(a[i].a);
        int y=get(a[i].b);
        if (x!=y){
            fa[y]=x;
            add(a[i].a,a[i].b,a[i].w);
            add(a[i].b,a[i].a,a[i].w);
        }
    }
}
void dfs(int u){
	d[u]=d[f[u][0]]+1;
	for (int i=h[u];i!=-1;i=ne[i]){
		int j=e[i];
		if (j==f[u][0]){
			continue;
        }
		f[j][0]=u;
        g[j][0]=w[i];//我们需要在dfs函数里给g[v][0]赋初值,为每个结点v上面那条边的权值。
		dfs(j);
	}
}
int lca(int x,int y){//LCA算法
    if (get(x)!=get(y)){//判断是否合法,在不在一个连通块内
        return -1;
    }
	if (d[x]<d[y]){
		swap(x,y);
	}
	int k=(int)(log2(d[x])),ans=0x7fffffff;//ans存答案
	for (int j=k;j>=0;j--){
		if (d[f[x][j]]>=d[y]){
            ans=min(ans,g[x][j]);//维护最大值
			x=f[x][j];
		}
	}
	if (x==y){/
		goto output;
	}
	k=(int)(log2(d[x]));
	for (int j=k;j>=0;j--){
		if (f[x][j]!=f[y][j]){
            ans=min(ans,min(g[x][j],g[y][j]));//维护最大值
			x=f[x][j];
			y=f[y][j];
		}
	}
    ans=min(ans,min(g[x][0],g[y][0]));//最后还差一步才到达lca,因此还要算上最后两条边的最大值
    output:
	return ans;
}
int main(){
    freopen("truck.in","r",stdin);
    freopen("truck.out","w",stdout);
    c_plus();
    int q,x,y;
    cin>>n>>m;
    init_list();
    init_set();
    _for(i,1,m){
        cin>>a[i].a>>a[i].b>>a[i].w;
    }
    kruskal();
    _for(i,1,n){
        if (d[i]==0){//每个连通块都要遍历
            dfs(i);
        }
    }
    for (int j=1;(1<<j)<=n;j++){
        _for(i,1,n){
            f[i][j]=f[f[i][j-1]][j-1];
            g[i][j]=min(g[i][j-1],g[f[i][j-1]][j-1]);//通过动态规划计算g数组剩余的值。
//转移方程很显然,就是把从i到f[i][j]这条链,拆成i到f[i][j-1]和f[i][j-1]到f[i][j]两条链,
//然后取最大值
        }
    }
    cin>>q;
    while (q--){
        cin>>x>>y;
        cout<<lca(x,y)<<endl;
    }
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值