【洛谷P1967】货车运输

题目

题目链接:https://www.luogu.com.cn/problem/P1967
A 国有 \(n\) 座城市,编号从 $1 $ 到 $ n$,城市之间有 \(m\) 条双向道路。每一条道路对车辆都有重量限制,简称限重。

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

思路

Kruskal 重构树模板题。
Kruskal 重构树一般解决的问题是“给出一张无向图,询问 \(x,y\) 两点之间所有路径中最短边长度最长的边的长度”。
我们将边权从大到小排序,在 Kruskal 求最大生成树时,如果 \(x',y'\) 两点并查集的根节点 \(x,y\) 不同,那么就建立一个新点 \(p\),从 \(p\) 分别向 \(x\)\(y\) 连边,然后将 \(x,y\) 两者的父亲都指向 \(p\)。点 \(p\) 的点权即为这条边的长度。
这样对于一组询问 \(x,y\),我们直接在 Kruskal 重构树上求出其 LCA,答案显然就是该点的权值。
时间复杂度 \(O(Q\log n+m)\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N=20010,M=50010,LG=18;
int n,m,Q,tot,val[N],head[N],father[N],dep[N],f[N][LG+1];

struct edge1
{
	int u,v,dis;
}e1[M];

struct edge2
{
	int next,to;
}e2[M];

void add(int from,int to)
{
	e2[++tot]=(edge2){head[from],to};
	head[from]=tot;
}

bool cmp(edge1 x,edge1 y)
{
	return x.dis>y.dis;
}

int find(int x)
{
	return x==father[x]?x:father[x]=find(father[x]);
}

void kruskal()
{
	sort(e1+1,e1+1+m,cmp);
	for (int i=1;i<n*2;i++) father[i]=i;
	for (int i=1;i<=m;i++)
	{
		int x=find(e1[i].u),y=find(e1[i].v);
		if (x!=y)
		{
			n++; val[n]=e1[i].dis;
			add(n,x); add(n,y);
			father[x]=father[y]=n;
		}
	}
}

void dfs(int x,int fa)
{
	dep[x]=dep[fa]+1; f[x][0]=fa;
	for (int i=1;i<=LG;i++)	
		f[x][i]=f[f[x][i-1]][i-1];
	for (int i=head[x];~i;i=e2[i].next)
		dfs(e2[i].to,x);
}

int lca(int x,int y)
{
	if (dep[x]<dep[y]) swap(x,y);
	for (int i=LG;i>=0;i--)
		if (dep[f[x][i]]>=dep[y]) x=f[x][i];
	if (x==y) return x;
	for (int i=LG;i>=0;i--)
		if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
		scanf("%d%d%d",&e1[i].u,&e1[i].v,&e1[i].dis);
	kruskal();
	for (int i=1;i<=n;i++)
		if (find(i)==i) dfs(i,0);
	scanf("%d",&Q);
	while (Q--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		if (find(x)!=find(y)) printf("-1\n");
			else printf("%d\n",val[lca(x,y)]);
	}
	return 0;
}
posted @ 2020-12-03 20:05  stoorz  阅读(47)  评论(0编辑  收藏  举报