让我们进入正题,这道题目所涉知识面是很广的。
先来列举一下这道题的知识点:最小生成树(最好是克鲁斯卡尔,也就是说我们还要用到并查集)+LCA(最好是倍增的)+(链式前向星)
*其实我们这道题要应用的是最大生成树,但是其实和最小生成树差异不大,所以在后文我还是叫最小生成树。
所以对上面知识有问题的同学,建议去看一下以下文章:
最小生成树详细解析
链式前向星
倍增求LCA
LCA连我自己都还没找到比较好的博客,所以,有时间自己复习梳理的时候写一篇。
给出你一幅图和一些边,每条边有一些权值,两两城市间会有不止一条边
问你从x城市到y城市的所有可行的路径方案中,最小边的最大值是多少
若两城市不连通则输出-1
对于 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。
因为数据比较大,所以我们这题不讲部分分。
因为直接暴力不是0分就是玄学水到20。
两个步骤,最小生成树+LCA(最近公共祖先)
相对于prim,克鲁斯卡尔会快一点。所以我们选择性能优异的后者,而后者的思想主要是这样的:
先排序,找到从小到大的权值的边的序列。(因为是最大生成树,所以这里是从大到小。
而我们要一个个去处理这些边,如果这些边的起始点和终点并不连接的话,我们就要保留这条边,所以在这里我们要用到并查集。我们可以将进行合并操作,使得这棵树的边数最好缩小到节点数-1,而联通所有的节点。
if(find(a[i].u)!=find(a[i].v))
fa[find(a[i].u)]=find(a[i].v)
并且,我们在这里可以用到链式前向星存边了。可以比矩阵快一点。
add(a[i].u,a[i].v,a[i].dis)
我们知道,这样建出来的树一定是可以求出符合题意答案的树,所以我们直接考虑找答案。
为了让我们的答案不让别的因素影响。
举个例子,如果多走了一条边,这条边的边权比我们的答案要大,其实对我们的答案没有影响,因为我们要选的答案是路径上权值最小的一条边。
如果这条边的权值比我们的答案要小,其实对我们的答案还有不利影响。
这里讲倍增的方法;
先预处理出每个节点的2的幂次祖先,然后在 O ( l o g ( n ) ) O(log(n)) O(log(n))的时间复杂度上求出路径的极值。然后,我们就要用到一个很重要的式子,它的用处是转移F数组的值:
f [ x ] [ i ] = f [ f [ x ] [ i − 1 ] ] [ i − 1 ] ; f[x][i]=f[f[x][i-1]][i-1]; f[x][i]=f[f[x][i−1]][i−1];
然后,为了符合题意,我们就要求LCA中的最小值。
在倍增的方法中,我们需要选择一种“跳”的方法,将两个数的点跳到同一层,这样可以使得我们求的LCA就是他们上一层的节点。
然后,我们还需要用到一个数组来记录一下就好了。
#include
using namespace std;
int n,m,q,x,y;
int fa[10005];
int next[20005],first[20005],go[20005],dis[20005],tot;
int dep[10005],f[10005][30],fm[10005][30];
struct node{
int u,v,dis;
}a[50005];
int read()
{
int f=1,x=0;
char s=getchar();
while(s<'0'||s>'9')
{
if(s=='-') f=-1;
s=getchar();
}
while(s>='0'&&s<='9') x=x*10+s-'0',s=getchar();
return x*f;
}
void qsort(int l,int r)
{
int i=l,j=r,mid=a[(l+r)/2].dis;
while(i<=j)
{
while(a[i].dis>mid) i++;
while(a[j].dis<mid) j--;
if(i<=j) swap(a[i],a[j]),i++,j--;
}
if(l<j) qsort(l,j);
if(i<r) qsort(i,r);
}
void add(int u,int v,int len)//链式前向星
{
next[++tot]=first[u];first[u]=tot;go[tot]=v,dis[tot]=len;
next[++tot]=first[v];first[v]=tot;go[tot]=u,dis[tot]=len;
}
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
void ready(int o,int from)
{
dep[o]=dep[from]+1;
for(int i=1;i<=20;i++){
f[o][i]=f[f[o][i-1]][i-1];
fm[o][i]=min(fm[f[o][i-1]][i-1],fm[o][i-1]);
}
for(int i=first[o];i;i=next[i]){
int v=go[i];
if(v==from)continue;
fm[v][0]=dis[i];
f[v][0]=o;
ready(v,o);
}
}
int lca(int x,int y)
{
int minx=0x7fffffff;
if(dep[y]>dep[x]) swap(x,y);
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y]){
minx=min(minx,fm[x][i]);
x=f[x][i];
}
if(x==y) return minx;
}
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
minx=min(minx,min(fm[x][i],fm[y][i]));
x=f[x][i];
y=f[y][i];
}
}
return min(minx,min(fm[x][0],fm[y][0]));
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1;i<=m;++i) a[i].u=read(),a[i].v=read(),a[i].dis=read();
qsort(1,m);
for(int i=1;i<=m;++i)
if(find(a[i].u)!=find(a[i].v))
fa[find(a[i].u)]=find(a[i].v),add(a[i].u,a[i].v,a[i].dis);
ready(1,0);
q=read();
for(int i=1;i<=q;++i)
{
x=read(),y=read();
if(find(x)!=find(y)) printf("-1\n");
else printf("%d\n",lca(x,y));
}
return 0;
}