【洛谷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;
}