#倍增,LCA,Kruskal#JZOJ 1092 洛谷 4180 [BJOI 2010] 次小生成树

题目

给出一个无向图,问它的严格次小生成树(数据保证有解)


分析

首先先找一棵最小生成树,然后对于每条非树边\((x,y,w)\)可以找到最小生成树上\(x,y\)路径上略小于\(w\),首先如果能找到那么这条边肯定不超过\(w\),因为如果超过\(w\)肯定可以被非树边代替
但是如果上面找到的最大边等于\(w\),就不能保证严格次小了
总结一下思路,就是维护最大和次大的边,这可以通过倍增实现,在\(x,y\)跳到\(LCA\)的过程中同时可以求出最大边和次大边
先讲一下如何倍增(\(g[x][i][0/1]\)表示\(x\)\(2^i\)步中的最大/次大边)
\(g[x][i][0]=max(g[x][i-1][0],g[f[x][i-1]][i-1][0])\)
\(g[x][i][1]=max(g[x][i-1][0],g[f[x][i-1]][i-1][0],g[x][i-1][1],g[f[x][i-1]][i-1][1])\)
\(g[x][i][1]\)里面的是选择第二大的,优先用前面两个,前两个相等就用后两个,哪个大就拿它的下一个
这样必须保证\(g[x][i][0]>g[x][i][1]\)(代码有一点不足就是\(g[1][0][0]=g[1][0][1]\),但是貌似没有什么问题)
注意一下细节,我90分改了很久,有两个地方有小错误。在代码中会讲


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
const int N=100011;
struct rec{int x,y,w;}E[N*3]; long long sum,ans=1e16;
struct node{int y,w,next;}e[N<<1]; bool is_tree[N*3];
int bas,n,m,ls[N],fat[N],g[N][18][2],f[N][18],dep[N],k=1;
inline signed iut(){
    rr int ans=0; rr char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans;
}
inline signed max(int a,int b){return a>b?a:b;}
bool cmp(rec x,rec y){return x.w<y.w;}
inline signed getf(int u){return fat[u]==u?u:fat[u]=getf(fat[u]);}
inline void add(int x,int y,int w){
    e[++k]=(node){y,w,ls[x]},ls[x]=k;
    e[++k]=(node){x,w,ls[y]},ls[y]=k;
}
inline void Kruskal(){
    rr int cnt=0;
    for (rr int i=1;i<=n;++i) fat[i]=i;
    for (rr int j=1;j<=m;++j){
        rr int fa=getf(E[j].x),fb=getf(E[j].y);
        if (fa==fb) continue;
        if (fa>fb) fa^=fb,fb^=fa,fa^=fb;
        fat[fa]=fb,add(E[j].x,E[j].y,E[j].w);
        is_tree[j]=1,sum+=E[j].w;
        if (++cnt==n-1) return;
    }
}
inline void dfs(int x,int fa,int p){
    dep[x]=dep[fa]+1,f[x][0]=fa,
    g[x][0][0]=(p==1)?-2e9:e[p].w,g[x][0][1]=-2e9;
    for (rr int i=ls[x];i;i=e[i].next)
    if (e[i].y!=fa) dfs(e[i].y,x,i);
}
inline void upd(int &t1,int &t2,int z1,int z2){
    if (t1<z1) t2=max(t1,z2),t1=z1;
        else if (t2<z1&&t1>z1) t2=z1;
            else if (t2<z2&&t1>z2) t2=z2;
    //原来是只有第二行为if (t2<z1) t2=z1(一定要保证t1>t2)
    //HACK数据From LOJ:(答案是800,701就是上面那种情况,oo我就不知道了)
    // 9 9
    // 1 2 100
    // 2 3 100
    // 3 4 100
    // 4 5 100
    // 1 6 1
    // 6 7 100
    // 7 8 100
    // 8 9 100
    // 5 9 100
}
inline signed wLCA(int x,int y,int w){
    rr int t1=-2e9,t2=-2e9;
    if (dep[x]<dep[y]) x^=y,y^=x,x^=y;
    for (rr int i=bas;~i;--i)
    if (dep[f[x][i]]>=dep[y])
        upd(t1,t2,g[x][i][0],g[x][i][1]),x=f[x][i];
    for (rr int i=bas;~i;--i)
    if (f[x][i]!=f[y][i]){
        upd(t1,t2,g[x][i][0],g[x][i][1]),
        upd(t1,t2,g[y][i][0],g[y][i][1]),
        x=f[x][i],y=f[y][i];
    }
    if (x!=y){
        upd(t1,t2,g[x][0][0],g[x][0][1]),
        upd(t1,t2,g[y][0][0],g[y][0][1]),
        x=f[x][0],y=f[y][0];
    }
    if (t1==w&&t2!=-2e9) return w-t2;
        else if (t1!=w&&t1!=-2e9) return w-t1;//如果t1是-2e9,那么int溢出然后就变成负数了
            else return 2e9;
}
signed main(){
    n=iut(); m=iut();
    for (;(1<<bas)<=n;++bas);
    for (rr int i=1;i<=m;++i){
        rr int X=iut(),Y=iut(),W=iut();
        E[i]=(rec){X,Y,W};
    }
    sort(E+1,E+1+m,cmp);
    Kruskal(),dfs(1,0,1);
    for (rr int j=1;j<=bas;++j)
    for (rr int i=1;i<=n;++i){
        f[i][j]=f[f[i][j-1]][j-1];
        g[i][j][0]=max(g[i][j-1][0],g[f[i][j-1]][j-1][0]);
        if (g[i][j-1][0]==g[f[i][j-1]][j-1][0]) g[i][j][1]=max(g[i][j-1][1],g[f[i][j-1]][j-1][1]);
            else if (g[i][j-1][0]<g[f[i][j-1]][j-1][0]) g[i][j][1]=max(g[i][j-1][0],g[f[i][j-1]][j-1][1]);
                else g[i][j][1]=max(g[i][j-1][1],g[f[i][j-1]][j-1][0]);
    }
    for (rr int i=1;i<=m;++i)
    if (!is_tree[i]){
        rr long long t=sum+wLCA(E[i].x,E[i].y,E[i].w);
        if (ans>t) ans=t;
    }
    return !printf("%lld",ans);
}
posted @ 2020-02-07 14:05  lemondinosaur  阅读(119)  评论(0编辑  收藏  举报