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


 

!!!原来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('\n');
        if(dj_find(a)!=dj_find(b)) printf("-1");
        else printf("%d",lca(a,b));
    }
    return 0;
}
AC代码

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

posted @ 2018-09-15 11:55  Mr^Kevin  阅读(266)  评论(0编辑  收藏  举报