题目描述
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<5×104,1≤q<1000;
-
对于 100% 的数据,1≤n<104,1≤m<5×104,1≤q<3×104,0≤z≤105。
思路
- 首先,要使最小值最大,而有好多条边,那么我们很容易想到求出这个图的最大生成树(因为假如走的不是树边,那么载重显然更小,与我们要求的不符)。
- 重构出最大生成树后,要求的就变成了求树上任意两点间的最小值,我们可以通过求lca来求。
算法流程
- 建有向图,用Kruskal求出最大生成树的树边,用这些树边建一个新的无向图。
- dfs预处理出
fa[x][i]
(x的2i辈祖宗)和mi[x][i]
(x到2i辈祖宗经过的最小树边)(注意:图可能是不联通的,所以预处理时要对每棵树都dfs一遍。)。 - 求出x,y的lca,把x到y的路径拆分成两部分:x到lca,y到lca。分别通过倍增求出这两条路径的最小树边,然后再取min,就是最终答案了。
代码
#include<cstdio>
#include<algorithm>
#define ri register int
using namespace std;
const int maxn=1e5+7;
const int inf=0x7fffffff;
struct E1{
int u,v,w,n;
}e1[maxn];
int head1[maxn];//原图
struct E{
int v,w,n;
}e[maxn<<1];
int head[maxn];//新树
int f[maxn];//代表元素
int d[maxn],lg[maxn];//深度,lg
int mi[maxn][37];//最小值
int fa[maxn][37];//祖宗
int n,m,cnt1,cnt;
inline int read(){//快读
int x=0,f=0;char c=getchar();
while(c>'9'||c<'0') f=c=='-',c=getchar();
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return f?-x:x;
}
inline void add1(int u,int v,int w){//原图
e1[cnt1].u=u;
e1[cnt1].v=v;
e1[cnt1].w=w;
e1[cnt1].n=head1[u];
head1[u]=cnt1;
cnt1++;
}
inline void add(int u,int v,int w){//新树
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].n=head[u];
head[u]=cnt;
}
bool cmp(E1 x,E1 y){//快排的cmp
return x.w>y.w;
}
int get(int x){//并查集
if(x==f[x]) return x;
return f[x]=get(f[x]);
}
void Kruskal(){//求最大生成树
sort(e1,e1+cnt1,cmp);
for(ri i=0;i<m;++i)
{
int u=e1[i].u,v=e1[i].v,w=e1[i].w;
if(get(u)==get(v)) continue;
f[get(u)]=get(v);
add(u,v,w),add(v,u,w); //新树建双边
}
}
void dfs(int u,int fath){//预处理
for(ri i=1;i<=lg[d[u]]+1;++i)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
mi[u][i]=min(mi[fa[u][i-1]][i-1],mi[u][i-1]);
}
for(ri i=head[u];~i;i=e[i].n)
{
int v=e[i].v,w=e[i].w;
if(e[i].v==fath) continue;
d[v]=d[u]+1;
fa[v][0]=u;
mi[v][0]=w;
dfs(v,u);
}
}
int lca(int x,int y){//lca
if(d[x]<d[y]) return lca(y,x);
while(d[x]>d[y]) x=fa[x][lg[d[x]-d[y]]];
if(x==y) return x;
for(ri i=lg[d[x]];i>=0;--i)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int find(int x,int l){//求最小值
int minx=inf;
while(d[x]>d[l])
{
minx=min(minx,mi[x][lg[d[x]-d[l]]]);
x=fa[x][lg[d[x]-d[l]]];
}
return minx;
}
int main(){
n=read(),m=read();
for(ri i=1;i<=n;++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
for(ri i=1;i<=n;++i) lg[i]--,f[i]=i,head[i]=-1;//预处理lg
for(ri i=1;i<=m;++i)
{
int u=read(),v=read(),w=read();
add1(u,v,w);//原图只用建单边
}
Kruskal();
for(ri i=1;i<=n;++i)
if(f[i]==i) d[i]=1,fa[i][0]=i,mi[i][0]=inf,dfs(i,0);
//每棵树都要预处理
int q=read();
while(q--)
{
int x=read(),y=read();
if(get(x)!=get(y)) printf("-1\n");
else
{
int l=lca(x,y);
printf("%d\n",min(find(x,l),find(y,l)));//分两部分
}
}
return 0;
}