【计蒜客习题】蒜头君运送宝藏
!!!原来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; }
之所以代码里面那样做可以,是因为最大生成树所不包含的点仅仅是那些孤零零的点,而并查集本质就是一棵树(树的结点只有一个父亲,必须满足这个条件),这样并查集里父亲是自身的就是树根了(不唯一,不同的方法可以找到不同的树根)。两点之间无法到达就是指其中一个是孤零零的点。