其他分享
首页 > 其他分享> > 题目Luogu 4-P1967 货车运输

题目Luogu 4-P1967 货车运输

作者:互联网

题目链接

依旧是很裸的题干


题干很清楚,肯定是图论

一开始想到单源最长路,用贝尔曼福德算法,全部边权变成相反数,后来发现不可行

因为这个题目要找的路径是边权最小值最大

最小值最大——二分

但是如果对于每个询问都二分一次再check,复杂度来到了O(n2logn)无法接受

(题解里好像有对询问排序做的nlogn二分,但是不会写,果然二分没学好)


那么应该怎么解决呢

想想如果我们是货车司机

只要不是傻子,肯定不会走载重(边权)小的路啊,哪怕绕远,只要经过的路径载重越大越好

那我们就从图里剔除边权小的且一定不会走过的边

边权小好说,怎么知道一定不会走过呢?

注意到给定的图可能有多个连通块,对于一个连通块,在剔边之后必须还是一个连通块且不能再剔除边

发现了什么?这是一个树!从小往大剔除,就是从大往小增加

由此得出第一步操作:建图,对于每个连通块跑最大生成树。

for(int i=1;i<=m;i++){
	int x,y,z;
	cin>>x>>y>>z;
	ef[cnt].u=x,ef[cnt].v=y,ef[cnt++].w=z;
}
sort(ef,ef+cnt,cmp);
int k=0;
for(int i=0;i<cnt;i++){
	if(father(ef[i].u)!=father(ef[i].v)){
		Union(ef[i].u,ef[i].v);
		e[ef[i].u].push_back(ef[i]);
		e[ef[i].v].push_back((E){ef[i].v,ef[i].u,ef[i].w});
		k++;
	}
	if(k==n-1)	break;
}

然后开始处理询问

既然跑最大生成树时用到了并查集,那么处理这个就好办了

如果货车不能到达目的地,输出 -1。

if(father(a)!=father(b))
		cout<<"-1"<<endl;

然后现在就能确保两个地点在一个最大生成树上,那怎么知道途径路径边权的最小值呢?

注意到树上两个点最短路径是唯一的,无论树怎样旋转

所以我们只需要让两个点一路向根移动,并同时记录边权最小值,直到在交汇点——它们的LCA,再对两个答案取min并输出

如何O(1)的查询到一个孩子节点到它父亲节点边的边权?用fapath[i]表示节点i的父节点到节点i的这条边,在<vector>e[i的父节点]中的下标

即在建边的时候,顺便记录fapath,查询的时候查询e[fa[now][0]][fapath[now]].w即可

至此,题目完整思路呈现:对每个连通块跑最大生成树,存树;对于每组询问,先用并查集判连通性,再在树上求LCA,遍历取min即可

完整代码

// Problem: P1967 [NOIP2013 提高组] 货车运输
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1967
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define INF 0x7fffffff
#define MAXN 10050
#define MAXM 50050
using namespace std;
struct E{
	int u,v,w;
}ef[MAXM];
vector<E> e[MAXN];
int n,m,cnt;
int jh[MAXN],fa[MAXN][21],depth[MAXN],fapath[MAXN];
int father(int x){
	return jh[x]==0?x:jh[x]=father(jh[x]);
}
void Union(int x,int y){
	int fx=father(x),fy=father(y);
	if(fx!=fy)	jh[fx]=fy;
}
bool cmp(E p,E q){
	return p.w>q.w;
}
void dfs(int now,int fath){
	if(fath!=-1){
		fa[now][0]=fath;
		depth[now]=depth[fath]+1;
		for(int i=1;i<=log2(depth[now]);i++){
			fa[now][i]=fa[fa[now][i-1]][i-1];
		}
	}
	for(int i=0;i<e[now].size();i++){
		if(e[now][i].v!=fath){
			fapath[e[now][i].v]=i;
			dfs(e[now][i].v,now);
		}
	}
}
int LCA(int x,int y){
	if(depth[x]<depth[y]){
		swap(x,y);
	}
	while(depth[x]>depth[y]){
		x=fa[x][(int)log2(depth[x]-depth[y])];
	}
	if(x==y)	return y;
	for(int i=log2(depth[x]);i>=0;i--){
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	return fa[x][0];
}
int getans(int LCA,int x,int y){
	int ans=INF;
	while(x!=LCA){
		ans=min(ans,e[fa[x][0]][fapath[x]].w);
		x=fa[x][0];
	}
	while(y!=LCA){
		ans=min(ans,e[fa[y][0]][fapath[y]].w);
		y=fa[y][0];
	}
	return ans;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		ef[cnt].u=x,ef[cnt].v=y,ef[cnt++].w=z;
	}
	sort(ef,ef+cnt,cmp);
	int k=0;
	for(int i=0;i<cnt;i++){
		if(father(ef[i].u)!=father(ef[i].v)){
			Union(ef[i].u,ef[i].v);
			e[ef[i].u].push_back(ef[i]);
			e[ef[i].v].push_back((E){ef[i].v,ef[i].u,ef[i].w});
			k++;
		}
		if(k==n-1)	break;
	}
	int q;
	cin>>q;
	for(int i=1;i<=n;i++){
		if(father(i)==i){
			depth[i]=1;
			dfs(i,-1);
		}
	}
	while(q--){
		int a,b;
		cin>>a>>b;
		if(father(a)!=father(b)){
			cout<<"-1"<<endl;
		}else{
			cout<<getans(LCA(a,b),a,b)<<endl;
		}
	}
	return 0;
}

 

标签:cnt,int,Luogu,边权,ef,货车运输,father,fa,P1967
来源: https://www.cnblogs.com/XHZS-XY/p/16516277.html