• 【计蒜客习题】蒜头君运送宝藏



    !!!原来LCA的题可以出的这么难,完了这还属于水题?!

    先来解释一下题意,有N个城市,在这N城市之间有M条边(不一定每个城市都有边)。我们的任务是找出给定两个点之间路径上的最小边权,使得这个最小边权尽量大。一开始我很纳闷,这和LCA有什么关系呢,怎么和最大流有点像。冥思苦想(看了别人的想法)之后,哦,原来是最大生成树+LCA。。。因为是最大生成树,可以使得任意两点间路径上的最大边权最大(再连上较小的会成环)。这样问题就变成了找树上两点间路径上的最小边权,显然可以利用LCA,这是他十分经典的应用。设minn[i][j]表示从结点i向上蹦2^j次,中间路径上的最小边权,则minn[i][0]=w<father[i],i>(从其父亲到结点i的边权),minn[i][j]=min(minn[i][j-1],minn[fa[i][j-1]][j-1])(j!=0),相当于将路径二分。

    这道题最纠结的在于一开始建树的过程,是用Kruskal没有疑问,但是真的是建无向图吗?树根怎么确定?修改了好久才弄对,代码及其。。。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int maxn=1e4+5,maxm=5e4+5;
    int n,m,head[maxn],eid;
    struct edge {
        int u,v,w;
        bool operator < (const edge& rhs) const {
            return w>rhs.w;
        }
    } E[maxm];
    struct edge2 {
        int v,w,next;
    } e[2*maxn];
    void insert(int u,int v,int w) {
        e[eid].v=v,e[eid].w=w;
        e[eid].next=head[u];
        head[u]=eid++;
    }
    int dj_fa[maxn];
    int dj_find(int i) {
        if(i==dj_fa[i]) return i;
        return dj_fa[i]=dj_find(dj_fa[i]);
    }
    void dj_merge(int a,int b) {
        a=dj_find(a),b=dj_find(b);
        if(a!=b) dj_fa[b]=a;
    }
    int vis[maxn],d[maxn],fa[maxn][20],minn[maxn][20];
    void dfs(int u) {
        for(int p=head[u];p+1;p=e[p].next) {
            int v=e[p].v;
            if(d[v]!=-1) continue;
            d[v]=d[u]+1;
            fa[v][0]=u;
            minn[v][0]=e[p].w;
            dfs(v);
        }
    }
    int lca(int x,int y) {
        int i,j,mind=0x3f3f3f3f;
        if(d[x]<d[y]) swap(x,y);
        for(i=0;(1<<i)<=d[x];++i);--i;
        for(j=i;j>=0;--j) if(d[x]-(1<<j)>=d[y]) {
            mind=min(mind,minn[x][j]);
            x=fa[x][j];
        }
        if(x==y) return mind;
        for(j=i;j>=0;--j) if(fa[x][j]!=fa[y][j]) {
            mind=min(mind,min(minn[x][j],minn[y][j]));
            x=fa[x][j],y=fa[y][j];
        }
        mind=min(mind,min(minn[x][0],minn[y][0]));
        return mind;
    }
    int main() {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;++i) scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
        sort(E+1,E+m+1);
        for(int i=1;i<=n;++i) dj_fa[i]=i;
        memset(head,-1,sizeof(head));
        for(int i=1;i<=m;++i) {
            int u=E[i].u,v=E[i].v,w=E[i].w;
            if(dj_find(u)!=dj_find(v)) {
                dj_merge(u,v);
                insert(u,v,w);insert(v,u,w);
                vis[u]=vis[v]=1;
            }
        }
        int root;
        for(int i=1;i<=n;++i) if(dj_fa[i]==i&&vis[i]) root=i;
        memset(d,-1,sizeof(d));d[root]=0;
        dfs(root);
        for(int j=1;(1<<j)<=n;++j)
            for(int i=1;i<=n;++i) {
                fa[i][j]=fa[fa[i][j-1]][j-1];
                minn[i][j]=min(minn[i][j-1],minn[fa[i][j-1]][j-1]);
            }
        int q,a,b,first=1;
        scanf("%d",&q);
        for(int i=1;i<=q;++i) {
            scanf("%d%d",&a,&b);
            if(first) first=0;
            else putchar('
    ');
            if(dj_find(a)!=dj_find(b)) printf("-1");
            else printf("%d",lca(a,b));
        }
        return 0;
    }
    AC代码

    之所以代码里面那样做可以,是因为最大生成树所不包含的点仅仅是那些孤零零的点,而并查集本质就是一棵树(树的结点只有一个父亲,必须满足这个条件),这样并查集里父亲是自身的就是树根了(不唯一,不同的方法可以找到不同的树根)。两点之间无法到达就是指其中一个是孤零零的点。

  • 相关阅读:
    iOS Hardware Guide
    安卓上为什么不能用system.io.file读取streammingAssets目录下的文件
    【转】【Unity+Lua】实测如何性能优化(Lua和C#交互篇)
    随手记:IDAPro蛮强大
    断线重连
    稀土掘金
    C#利用WebService接口下载文件
    C# sbyte[]转byte[]
    百度地图API示例 JS
    如何才能成为一个好的技术领导者?
  • 原文地址:https://www.cnblogs.com/Mr94Kevin/p/9650467.html
Copyright © 2020-2023  润新知