暑假考试题5:tree 最小生成树(最小生成树+倍增)

题目:

分析:

转换问题:先求出一颗最小生成树
对于非树边来说,连上非树边一定会成环,而可以使非树边成为树边的即是环上max-1
对于树边来说,它不能无限增大,是由于受到非树边的影响,而答案即为能影响它的非树边的min-1

第一个只需要求链最大值,用倍增解决
第二个:对于每一条非树边,都要更新一条链,但是可以不用写链剖
做法:将非树边从小到大排好序,显然一条边先被小的更新了,就不会被大的更新了,所以开一个ff数组
表示一个点到深度比它浅的点那条路径(不经过根)已经被更新了 当枚举非树边去更新的时候 把已经被更新的跳过
保证了每条边只被更新过一次

/*
先求出一颗最小生成树
对于非树边来说,连上非树边一定会成环,而可以使非树边成为树边的即是环上max-1 
对于树边来说,它不能无限增大,是由于受到非树边的影响,而答案即为能影响它的非树边的min-1
第一个只需要求链最大值,用倍增解决
第二个:对于每一条非树边,都要更新一条链,但是可以不用写链剖
做法:将非树边从小到大排好序,显然一条边先被小的更新了,就不会被大的更新了,所以开一个ff数组
表示一个点到深度比它浅的点那条路径(不经过根)已经被更新了 当枚举非树边去更新的时候 把已经被更新的跳过
保证了每条边只被更新过一次 
*/
#include<bits/stdc++.h>
using namespace std;
#define inf 1000000001
#define N 100005
int head[N],w[N<<1],to[N<<1],nex[N<<1],tot=0,fa[N],ans[N],id[N<<1];
int f[N][30],mx[N][30],dep[N],num[N],s[N],ff[N],fl[N],n,m;
struct node{ int u,v,w,o,ans; }e[N];
bool cmp1(const node &a,const node &b) { return a.w<b.w; }
bool cmp2(const node &a,const node &b) { return a.o<b.o; }
void add(int a,int b,int ww,int i) { to[++tot]=b; nex[tot]=head[a]; w[tot]=ww; id[tot]=i; head[a]=tot; }
int find(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=find(fa[x]);
}
void dfs(int u)
{
    for(int i=head[u];i;i=nex[i]){
        int v=to[i];
        if(v==f[u][0]) continue;
        f[v][0]=u; dep[v]=dep[u]+1;
        mx[v][0]=w[i]; num[v]=id[i];//id记录这个点向上连的边对应排好序后的编号 
        for(int j=1;j<=20;j++) f[v][j]=f[f[v][j-1]][j-1];
        for(int j=1;j<=20;j++) mx[v][j]=max(mx[f[v][j-1]][j-1],mx[v][j-1]);
        dfs(v);
    }
}
int lca(int a,int b)
{
    if(dep[a]>dep[b]) swap(a,b);
    for(int i=20;i>=0;i--) if(dep[f[b][i]]>=dep[a]) b=f[b][i];//=!!
    if(a==b) return a;
    for(int i=20;i>=0;i--) if(f[a][i]!=f[b][i]) a=f[a][i],b=f[b][i];
    return f[a][0];
}
int lca_max(int a,int b,int lc)
{
    int anss=-1;
    if(dep[a]>dep[b]) swap(a,b);
    for(int i=20;i>=0;i--) if(dep[f[b][i]]>=dep[a]) anss=max(anss,mx[b][i]),b=f[b][i];//先更新再跳 一定要有dep>=!! 
    if(a==b) return anss;
    for(int i=20;i>=0;i--) if(f[a][i]!=f[b][i]) anss=max(anss,max(mx[a][i],mx[b][i])),a=f[a][i],b=f[b][i];
    anss=max(anss,max(mx[a][0],mx[b][0]));
    return anss;
}
void work(int u,int lc,int ww)
{
    while(dep[u]>dep[lc]){
        if(!s[num[u]] || s[num[u]]>ww) s[num[u]]=ww;//更新最小值 
        ff[u]=lc; u=f[u][0];//记录u已经更新到哪一个点了 u一定要=fa[u] 否则会在原地不动 
        while(ff[u]!=u) u=ff[u];
    }
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    int a,b,ww;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&a,&b,&ww);
        e[i].u=a,e[i].v=b,e[i].w=ww,e[i].o=i;
    }
    sort(e+1,e+1+m,cmp1);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++){
        int f1=find(e[i].u),f2=find(e[i].v);
        if(f1==f2) continue;
        fa[f1]=f2; fl[i]=1; 
        add(e[i].u,e[i].v,e[i].w,i); add(e[i].v,e[i].u,e[i].w,i);
    }
    dep[1]=1;
    dfs(1);
    for(int i=1;i<=n;i++) ff[i]=i;//ff数组记录每个点更新到哪一个点了 初始时为它自己  
    for(int i=1;i<=m;i++){
        if(fl[i]) continue;
        int u=e[i].u,v=e[i].v,ww=e[i].w;
        int lc=lca(u,v),maxx=lca_max(u,v,lc);
        ans[i]=maxx-1;//ans存储非树边的值 
        while(ff[u]!=u) u=ff[u];//直接跳到已经更新到的位置 
        while(ff[v]!=v) v=ff[v];
        work(u,lc,ww);//处理将u到v的一条链更新成最小的非树边的值 
        work(v,lc,ww);
    }
    for(int i=1;i<=m;i++){
        if(!fl[i]) e[i].ans=ans[i];
        else e[i].ans=s[i]-1;
    }
    sort(e+1,e+1+m,cmp2);
    for(int i=1;i<=m;i++) printf("%d
",e[i].ans);/**/
}
/*
4 4 
2 1 2 
3 2 2 
4 3 2 
1 4 3

7 8
1 2 2
1 4 3
2 3 6
2 5 8
2 7 5
1 7 9
3 6 7
2 4 4

*/
原文地址:https://www.cnblogs.com/mowanying/p/11420174.html