- 【问题描述】
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。
- 【输入样例】
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
- 【输出样例】
3
-1
3
- 【数据范围】
对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q < 1,000;
对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q < 1,000;
对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q < 30,000,0 ≤ z ≤ 100,000。
【思路梳理】
典型的最小值最大问题。大致思路有两种,一种是直接运用并查集。结合并查集和贪心算法,不断地把边权值较大的边依次加入到空边图中,直到刚好连通,这时候我们所加的最后一条边的权值就是我们需要的答案。或者是结合二分猜答案的思想,利用边权值不断地去“掐”(假删除)边直到起点和终点都恰好连通。这两种方法对于q=30,000的极限情况来说,时间耗费明显过于庞大。
此时可以考虑直接生成一颗最大树,先将所有边依次加入到空边图中,将所有结点连接起来,形成一个总权值尽可能大的树(注意是树,任意两个节点都应该有且仅有一条路径相连接),那么对于每一个询问,我们都保证了路上每一条边的权值尽可能大,那么此时的答案一定是最优的,运用LCA算法和爬山思想,出发点和目的地结点不断地往上直到最近公共祖先,整个过程中边权值最小的一条边就是我们的答案。说得抽象,直接给出Cpp代码。
【CPP代码】
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 10005
#define maxm 50005
#define inf 100005
using namespace std;
struct edge
{
int u,v,w;
};
int n,m,q,fa[maxn],pa[maxn],dept[maxn];
long long dist[maxn];
vector<edge>E;
vector<int>g[maxn],w[maxn];
bool link[maxn];
int LCA(int a,int b)//求结点a,b的最近公共结点的过程中求出这两点路径上边权值最小的边的权值
{
long long ans=inf;
if(dept[a]<dept[b]) swap(a,b);//保证a的深度比b要大,方便接下来将a,b爬至同一深度
while(dept[a]!=dept[b])//先爬a
{
ans=min(ans,dist[a]-dist[pa[a]]);//ans记录的是边权值最小的边的权值
a=pa[a];
}
while(a!=b)//a,b一起爬山
{
ans=min(ans,dist[a]-dist[pa[a]]);
a=pa[a];
ans=min(ans,dist[b]-dist[pa[b]]);
b=pa[b];
}
if(ans!=inf) return ans;
return -1;
}
void DFS(int i)//DFS一遍后帮助等下的LCA函数
{
for(int j=0;j<g[i].size();j++)
{
int k=g[i][j];
if(pa[k]) continue;
dept[k]=dept[i]+1;
dist[k]=dist[i]+w[i][j];
pa[k]=i;
link[k]=true;
DFS(k);
}
}
bool cmp(edge a,edge b)
{
return a.w>b.w;
}
int find(int x)
{
if(fa[x]==x) return x;
int root=find(fa[x]);
fa[x]=root;
return root;
}
void Union(int x,int y)
{
fa[find(x)]=find(y);
}
void initial()
{
for(int i=1;i<=n;i++) fa[i]=i;
}
void Kruskal()//建立最大生成树
{
initial();
sort(E.begin(),E.end(),cmp);
int link=0;
for(int i=0;i<E.size();i++)
{
int x=E[i].u,y=E[i].v,z=E[i].w;
if(find(x)==find(y)) continue;
Union(x,y);
link++;
g[x].push_back(y);
w[x].push_back(z);
g[y].push_back(x);
w[y].push_back(z);
if(link==n-1) break;
}
}
int main()
{
freopen("bus.in","r",stdin);
freopen("bus.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
E.push_back((edge){x,y,z});
}
Kruskal();
dist[1]=0;
pa[1]=1;
dept[1]=1;
memset(link,false,sizeof(link));
link[1]=true;//link[i]判断i是否在最小生成树上
DFS(1);
cin>>q;
while(q--)
{
int s,d;
scanf("%d%d",&s,&d);
if(link[s]==false || link[d]==false)
{
printf("-1\n");
continue;
}
int t=LCA(s,d);
printf("%d\n",t);
}
return 0;
}