[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;
}
posted @ 2018-10-04 19:07  昤昽  阅读(89)  评论(0编辑  收藏  举报