【JZOJ5914】盟主的忧虑【LCA】【并查集】【BFS】
题目大意:
题目链接:https://jzoj.net/senior/#main/show/5914
题目图片:
http://wx4.sinaimg.cn/mw690/0060lm7Tly1fwqalfjtaej30j20d2gm3.jpg
http://wx3.sinaimg.cn/mw690/0060lm7Tly1fwqalfjtn0j30j50ec3yn.jpg
江湖由个门派组成,这些门派之间有条小道将他们连接起来,每条道路都以“尺”为单位去计量,武林盟主发现任何两个门派都能够直接或者间接通过小道连接。
虽然整个江湖是可以互相到达的,但是他担心有心怀不轨之徒破坏这个武林的安定,破坏小道,于是武林盟主又秘密地修建了条密道,但每条小道距离都不超过亿尺。
果不其然,最近一个名叫“太吾”的组织意欲破坏武林的小道,请你帮盟主想想办法,如果门派到门派的直连小道被破坏,从走到的所有路径中,经过密道的距离最少是多少?
//蒟蒻语文不好不会概括求轻喷QWQ
思路 :
70分做法:
对于一棵树,我们切断它的其中一条边,那么这棵树一定会被切成两个互不相连的两棵树
。而这切开的边连接的两个点肯定会分别在两棵新的数内。那么符合要求的新边就肯定是连接这两棵树的边中边权最小的边。
那么就先枚举每一条边,然后再并查集找到这两棵树,再枚举每条新边,如果这条边连接着两棵树,那么久用这条边的边权和答案取最小值。
时间复杂度:
100分做法:
先把新边按边权从小到大排序。
我们知道,如果在一棵树上增加一条新边,那么就会形成一个环。那么这个环上的所有没有答案的边的答案就是这条边。因为在这个环上去任意一条边(新边除外),环上的点还是可以互相到达。那么由于已经把新边按照边权排序了,所以可以直接将答案赋值上去。
然后用并查集表示有哪些边已经被合并了。这样到时候向上跳的时候就可以直接将合并过的点跳过。
那么就将新边连接的两个端点取,分别往上跳,知道跳过(可以视为深度比低就停下)。然后跳到的边就赋值答案。
还有的时候会爆栈,所以就用代替即可。
时间复杂度:
代码(可读性):
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define N 100100
#define LG 20
using namespace std;
int n,m,tot;
int father[N],head[N],ans[N],pre[N],f[N][LG+1],dep[N];
struct edge
{
int next,to,num;
}e[N*2];
struct node
{
int x,y,dis;
}a[N];
void add(int from,int to,int x)
{
e[++tot].to=to;
e[tot].num=x;
e[tot].next=head[from];
head[from]=tot;
}
void bfs()
{
queue<int> X;
queue<int> F; //用队列代替栈
X.push(1);
F.push(0);
while (X.size())
{
int x=X.front();
int fa=F.front();
dep[x]=dep[fa]+1;
f[x][0]=fa;
for (int i=1;i<=LG;i++)
f[x][i]=f[f[x][i-1]][i-1]; //求出LCA的f数组
for (int i=head[x];~i;i=e[i].next)
if (e[i].to!=fa)
{
pre[e[i].to]=e[i].num;
X.push(e[i].to);
F.push(x);
}
X.pop();
F.pop();
}
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
for (int i=LG;i>=0;i--)
if (dep[f[x][i]]>=dep[y]) x=f[x][i];
if (x==y) return x;
for (int i=LG;i>=0;i--)
if (f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
int find(int x)
{
return father[x]==x?x:father[x]=find(father[x]);
}
bool cmp(node x,node y)
{
return x.dis<y.dis;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
int x,y,l;
for (int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y,i);
add(y,x,i);
ans[i]=-1; //记录答案
}
for (int i=1;i<=m;i++)
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].dis);
bfs();
sort(a+1,a+1+m,cmp); //按权值排序
for (int i=1;i<=n;i++)
father[i]=i; //并查集初始化
for (int i=1;i<=m;i++)
{
l=lca(a[i].x,a[i].y);
for (x=find(a[i].x);dep[x]>dep[l];x=father[find(x)]) //往上跳
{
ans[pre[x]]=a[i].dis; //记录答案
father[find(x)]=find(f[find(x)][0]); //更新集合
}
for (y=find(a[i].y);dep[y]>dep[l];y=father[find(y)])
{
ans[pre[y]]=a[i].dis;
father[find(y)]=find(f[find(y)][0]);
}
}
for (int i=1;i<n;i++)
printf("%d\n",ans[i]);
return 0;
}