[NOIp2013] 货车运输
洛谷 P1967
好不容易啊!少有的能做(一)出(遍)来(A)的题!
基本思路不难确定了:
- 先求出整个图中的连通分量(并查集) , 求出每个连通块的最大生成树 \((Kruskal)\) , 然后利用类似于倍增求 \(LCA\) 的思想,求出答案
- 求最大生成树需要在每个连通块内建树,但只要进行一遍 \((Kruskal)\) 即可,因为不同的连通块之间无边,\(Kruskal\) 会在每个连通块内各建一棵树
- 可以利用间接排序求最大生成树
- 利用倍增的思想,设\(g[i][j]\) 为结点 \(i\) 向上跳 \(2^j\) 步经过的边中边权的最小值.则
- \(g[i][j]=min(g[i][j-1],g[f[i][j-1]][j-1]\)
- \(ans(u,v)=min( ans(u,lca(u,v) ) , ans(v,lca(u,v) ) )\)
- 求解 \(g\) 值过程中无需另写函数 , 求 \(LCA\) 过程中维护值即可
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 1e4+5,MAXM = 5e5+5,inf=(1<<30)-1;
int u[MAXM],v[MAXM],z[MAXM],r[MAXM],f[MAXN],h[MAXN],lg2[MAXN],Depth[MAXN],fa[MAXN][20],g[MAXN][20],MaxDepth=0,ect=0;
struct Edge
{
int to,w,link;
Edge(int t=0,int weight=0,int next=-1):to(t),w(weight),link(next){}
}e[MAXN<<1];
inline int read()
{
int f=1,x=0;char c=getchar();
while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
return f*x;
}
inline void insert(int from,int to,int w)
{
e[++ect]=Edge(to,w,h[from]);
h[from]=ect;
}
inline int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
void unite(int a,int b){f[find(a)]=find(b);}
bool cmp(const int& a,const int& b){return z[a] > z[b];}
void dfs(int u,int father,int depth,int dist)
{
fa[u][0]=father;g[u][0]=dist;
MaxDepth=max(MaxDepth,Depth[u]=depth);
for(int it=h[u],v;it!=-1;it=e[it].link)if((v=e[it].to)!=father) dfs(v,u,depth+1,e[it].w);
}
inline int query(int u,int v)
{
int ans=inf;
if(Depth[u]<Depth[v]) swap(u,v);
for(int i=lg2[Depth[u]];i>=0;i--)
if(Depth[fa[u][i]]>=Depth[v])
{
ans=min(ans,g[u][i]);
u=fa[u][i];
}
for(int i=lg2[Depth[u]];i>=0;i--)
if(fa[u][i]!=fa[v][i])
{
ans=min(ans,min(g[u][i],g[v][i]));
u=fa[u][i];v=fa[v][i];
}
return u==v?ans:min(ans,min(g[u][0],g[v][0]));
}
int main()
{
memset(z,0x7f,sizeof(z));
int n = read(),m = read();
for(int i=1;i<=m;i++)
{
r[i] = i;
u[i] = read();v[i] = read();z[i] = read();
}
sort(r+1,r+m+1,cmp);//r[i]里存储的时第 i 大的边的编号
int cnt=0,p=1;
memset(h,-1,sizeof(h));
for(int i=1;i<=n;i++) f[i]=i;//并查集的初始化!!!
while(p<=m && cnt<n-1)
{
int &a=u[r[p]],&b=v[r[p]],&c=z[r[p]];
if(find(a) != find(b))
{
cnt++;
unite(a,b);
insert(a,b,c);
insert(b,a,c);
}
p++;
}
for(int i=2;i<=n;i++) lg2[i]=lg2[i>>1]+1;
memset(fa,0,sizeof(fa));
for(int i=1;i<=n;i++)//如果 i 没有根,以 i 为根建树.
if(fa[i][0]==0) dfs(i,0,1,inf);
for(int j=1;j<=lg2[MaxDepth];j++)
for(int i=1;i<=n;i++)
{
fa[i][j]=fa[fa[i][j-1]][j-1];
g[i][j]=min(g[i][j-1],g[fa[i][j-1]][j-1]);
}
int Q=read();
for(int i=1;i<=Q;i++)
{
int a=read(),b=read();
if(find(a) != find(b))
{
printf("-1\n");
}
else
{
int ans=query(a,b);
printf("%d\n",ans);
}
}
return 0;
}