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



    !!!原来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代码

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

  • 相关阅读:
    HDOJ 4747 Mex
    HDU 1203 I NEED A OFFER!
    HDU 2616 Kill the monster
    HDU 3496 Watch The Movie
    Codeforces 347A A. Difference Row
    Codeforces 347B B. Fixed Points
    Codeforces 372B B. Hungry Sequence
    HDU 1476 Sudoku Killer
    HDU 1987 How many ways
    HDU 2564 词组缩写
  • 原文地址:https://www.cnblogs.com/Mr94Kevin/p/9650467.html
Copyright © 2020-2023  润新知