[解题笔记]NOIP2013tg 货车运输

题目描述

A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。

现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入格式

第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。

接下来 m 行每行三个整数 x,y,z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。
注意: x≠y,两座城市之间可能有多条道路。

接下来一行有一个整数 q,表示有 q 辆货车需要运货。

接下来 q 行,每行两个整数 x,y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,保证 x≠y

输出格式

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

输入输出样例

输入 

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

输出 

3
-1
3

说明/提示

对于 30% 的数据,1≤n<1000,1≤m<10,000,1≤q<1000;

对于 60% 的数据,1≤n<1000,1≤m<50000,1≤q<1000;

对于 100% 的数据,1≤n<10000,1≤m<50000,1≤q<30000,0≤z≤100000。

“左的错误”

一眼看到1≤n<10000就想到O(n*n)那我们可不可以每次询问的时候都直接跑一遍单源最短路?时间复杂度级别为O(n*n*log(n))

如果遇到前面跑过的点就直接输出,以此稍微优化一下

但是,这样的复杂度还是不能把娇弱的测评机哄开心qwq

思路

既然每次询问时临时处理可行性不高,那就想到预先全局处理,考虑往生成树方向思考

通过库斯凯尔算法可以得到一棵生成树,使得任意两点间的简单路径即为运量最大的路径

有了一棵树,只要找两点间的路径,想到lca

Code

#include<bits/stdc++.h>
#define INF 2147483647
using namespace std;
const int MAXN=50001;
int n,m;

struct inedg{
	int x,y,z;
}a[MAXN];
bool cmp(inedg a,inedg b){
	return a.z>b.z;
}
int f[MAXN];
int getfa(int x){
	if(f[x]==x)return x;
	return f[x]=getfa(f[x]);
}

int head[MAXN],En;
struct EDGE{
	int nxt,to,val;
}e[MAXN];
void addedge(int x,int y,int z){
	e[++En].nxt=head[x];
	e[En].to=y;
	e[En].val=z;
	head[x]=En;
}

bool v[MAXN];
int fa[MAXN][17],dep[MAXN],w[MAXN][17];
void dfs(int x){
	v[x]=1;
	for(int i=head[x];i;i=e[i].nxt){
		int to=e[i].to;
		if(!v[to]){
			fa[to][0]=x;
			dep[to]=dep[x]+1;
			w[to][0]=e[i].val;
			dfs(to);
		}
	}
}

int lca(int x,int y){
	if(getfa(x)!=getfa(y)){
		return -1;
	}//别漏了这种情况
	int ans=INF;
	if(dep[y]>dep[x])swap(x,y);
	for(int i=16;i>=0;--i){
		if(dep[fa[x][i]]>=dep[y]){
			ans=min(ans,w[x][i]);
			x=fa[x][i];
		}
	}
	if(x==y)return ans;
	for(int i=16;i>=0;--i){
		if(fa[x][i]!=fa[y][i]){
			ans=min(ans,min(w[x][i],w[y][i]));
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	ans=min(ans,min(w[x][0],w[y][0]));
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;++i){
		scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
	}
	
	sort(a,a+m,cmp);
	for(int i=1;i<=n;++i){
		f[i]=i;
	}
	for(int i=0;i<m;++i){
		int X=a[i].x,Y=a[i].y;
		int fx=getfa(X),fy=getfa(Y);
		if(fx==fy)continue;
		f[fx]=fy;
		addedge(X,Y,a[i].z);
		addedge(Y,X,a[i].z);
	}//kruskal算法生成树
	
	for(int i=1;i<=n;++i){
		if(!v[i]){
			fa[i][0]=i;
			dep[i]=1;
			w[i][0]=INF;
			dfs(i);
		}
	}
	for(int i=1;i<17;++i){
		for(int j=1;j<=n;++j){
			fa[j][i]=fa[fa[j][i-1]][i-1];
			w[j][i]=min(w[j][i-1],w[fa[j][i-1]][i-1]);
		}
	}//作lca预处理
	
	int Q;
	scanf("%d",&Q);
	while(Q--){
		int t1,t2;
		scanf("%d%d",&t1,&t2);
		printf("%d\n",lca(t1,t2));
	}//使用O(n*log(n))的复杂度抚慰测评机awa
  return 0;
}

小结

这种题目考验多种算法较为灵活的运用,通过题目要求找到方向,发挥每个算法的优势,使时间复杂度控制在要求之内

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值