HAPPY Studying OvO
题意:设最小生成树的边权之和为 $ sum$,严格次小生成树就是指边权之和大于 $sum$ 的生成树中最小的一个。
思路 : Kruskal + LCA
步骤:
1. 首先你得会 Kruskal 和 LCA
(不会的话请先去学习一下 QwQ)
2.Kruskal 求出最小生成树
3.LCA 初始化边权,存最大值和次大值
4. 求次小生成树, 至于严不严格 就是边权之和是大于 $sum$,还是边权之和大于等于 $sum$ 而已
First. Kruskal
将边 按照边权从小到大排序 , 扫描时 并查集判断,如果不在联通块内就连边
void Kruskal(){
    sort(a+1,a+1+m);//a 存的是初始化的边集
    fors(i,1,n) f[i]=i;//并查集
    fors(i,1,m){
        int as=find(a[i].u),bs=find(a[i].v);
        if(as != bs){
            cnt+=a[i].val;//最小生成树边权和
            f[bs]=as;
            add(a[i].u,a[i].v,a[i].val);
            add(a[i].v,a[i].u,a[i].val);//把最小生成树的边以邻接表存起来
            vis[i]=1;//存点
        }
    }
}
—
Second. LCA
树上倍增: 用 $bz[~]$存一下,$bz[i]~[j]$表示 $i$点上面的第 $2^j$个祖先
$minai[~]$存次大边权,$maxai[~]$存最大边权
void dfs(int u,int fa){
    bz[u][0]=fa;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(v == fa) continue;
        depth[v]=depth[u]+1ll;
        maxai[v][0]=edge[i].val;
        minai[v][0]=-inf;//先极小值
        dfs(v,u);//u 是目标节点,v 是起始节点
    }
}//dfs 建树
//minai 存次大边权,maxai 存最大边权
void cal(){
    fors(j,1,18)
        fors(i,1,n){
            bz[i][j]=bz[bz[i][j-1]][j-1];
            maxai[i][j]=max(maxai[i][j-1],maxai[bz[i][j-1]][j-1]);
            minai[i][j]=min(minai[i][j-1],minai[bz[i][j-1]][j-1]);//预处理
            if(maxai[i][j-1] > maxai[bz[i][j-1]][j-1]) 
                minai[i][j]=max(minai[i][j],maxai[bz[i][j-1]][j-1]);
            else if(maxai[i][j-1] < maxai[bz[i][j-1]][j-1]) 
                     minai[i][j]=max(minai[i][j],maxai[i][j-1]);//minai 存次大边权
        }
}
int lca(int x,int y){
    if(depth[x] < depth[y]) swap(x,y);
    for(int i=18;i>=0;--i)
        if(depth[bz[x][i]] >= depth[y])
            x=bz[x][i];
    if(x==y) return x;
    for(int i=18;i >= 0 ;--i)
        if(bz[x][i] != bz[y][i])
            x=bz[x][i],y=bz[y][i];
    return bz[x][0];
}
—
Third. 求严格次小生成树
遍历每条未选的边 $(u,v,val)$,对于这条路径,我们断掉最大边(肯定 $val$不超过最大边)。这就找到了次大生成树,但是不一定是严格的。所以我们计算这条链上边权的严格次大值,然后如果最大值和这条边权相等就断严格次大边。然后一直 $min$边权就 OK 了 OvO
int querymax(int u,int v,int maxs){
    int ans=-inf;
    ford(int i=18;i>=0;--i)
        if(depth[bz[u][i]] >= depth[v]){
            if(maxs != maxai[u][i]) ans=max(ans,maxai[u][i]);
            else ans=max(ans,minai[u][i]);//上述操作
            u=bz[u][i];
        }
    return ans;
}//求最大的 u 或 v
signed main()
{
    n=read(),m=read();
    fors(i,1,m)
        a[i].u=read(),a[i].v=read(),a[i].val=read();
    Kruskal();
    minai[1][0]=-inf;
    depth[1]=1;
    dfs(1,-1);
    cal();
    int ans=inf;
    fors(i,1,m){
        if(!vis[i]){//遍历未加入最小生成树的点
            int u=a[i].u,v=a[i].v,val=a[i].val;
            int lcas=lca(u,v),maxsu=querymax(u,lcas,val),maxsv=querymax(v,lcas,val);
            ans=min(ans,cnt-max(maxsu,maxsv)+val);//cnt 是最小生成树边权和
        }
    }
    printf("%lld\n",ans);
    return 0;
}
—
Finally . 整合起来就完成了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define int long long//由于精度要求 int-ll
#define fors(i,a,b) for(int i=(a);i<=(b);++i)
#define ford(i,a,b) for(int i=(a);i>=(b);--i)
#define min(x,y) ((x) < (y) ? (x) : (y))
#define max(x,y) ((x) < (y) ? (y) : (x))
#define swap(x,y) ((x)^=(y),(y)^=(x),(x)^=(y))
const int maxn=1e6+7;
const int inf=1ll<<60;
const int Size=1<<25;
inline char getch(){
    static char buf[Size],*p1=buf,*p2=buf;
    return p1==p2 && (p2=(p1=buf)+fread(buf,1,Size,stdin),p1==p2) ? EOF : *p1++;
}
int read(){
    int f=1,s=0;
    char c=getch();
    while(c<'0' || c>'9'){if(c=='-') f=-1; c=getch();}
    while(c<='9' && c>='0') {s=s*10+c-48;c=getch();}
    return s*f;
}
//卡常使我快乐 *v*
struct node
{
    int u,v,val,next;
    bool operator < (const node &ano) const {
        return val < ano.val;
    }
}edge[maxn<<1];
int m,n,tot,head[maxn],bz[maxn][19],maxai[maxn][19],minai[maxn][19],depth[maxn];
void add(int u,int v,int val){
    edge[++tot].v=v,edge[tot].u=u,edge[tot].next=head[u],head[u]=tot,edge[tot].val=val;
}
void dfs(int u,int fa){
    bz[u][0]=fa;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(v == fa) continue;
        depth[v]=depth[u]+1ll;
        maxai[v][0]=edge[i].val;
        minai[v][0]=-inf;
        dfs(v,u);
    }
}
void cal(){
    fors(j,1,18)
        fors(i,1,n){
            bz[i][j]=bz[bz[i][j-1]][j-1];
            maxai[i][j]=max(maxai[i][j-1],maxai[bz[i][j-1]][j-1]);
            minai[i][j]=min(minai[i][j-1],minai[bz[i][j-1]][j-1]);
            if(maxai[i][j-1] > maxai[bz[i][j-1]][j-1]) 
                minai[i][j]=max(minai[i][j],maxai[bz[i][j-1]][j-1]);
            else if(maxai[i][j-1] < maxai[bz[i][j-1]][j-1]) 
                     minai[i][j]=max(minai[i][j],maxai[i][j-1]);
        }
}
int lca(int x,int y){
    if(depth[x] < depth[y]) swap(x,y);
    for(int i=18;i>=0;--i)
        if(depth[bz[x][i]] >= depth[y])
            x=bz[x][i];
    if(x==y) return x;
    for(int i=18;i >= 0 ;--i)
        if(bz[x][i] != bz[y][i])
            x=bz[x][i],y=bz[y][i];
    return bz[x][0];
}
int querymax(int u,int v,int maxs){
    int ans=-inf;
    for(int i=18;i>=0;--i)
        if(depth[bz[u][i]] >= depth[v]){
            if(maxs != maxai[u][i]) ans=max(ans,maxai[u][i]);
            else ans=max(ans,minai[u][i]);
            u=bz[u][i];
        }
    return ans;
}
node a[maxn<<1];
int f[maxn];
int find(int x){
    return x==f[x] ? x : f[x]=find(f[x]);
}
bool vis[maxn<<1];
int cnt;
void Kruskal(){
    sort(a+1,a+1+m);
    fors(i,1,n) f[i]=i;
    fors(i,1,m){
        int as=find(a[i].u),bs=find(a[i].v);
        if(as != bs){
            cnt+=a[i].val;
            f[bs]=as;
            add(a[i].u,a[i].v,a[i].val);
            add(a[i].v,a[i].u,a[i].val);
            vis[i]=1;
        }
    }
}
signed main()
{
    n=read(),m=read();
    fors(i,1,m)
        a[i].u=read(),a[i].v=read(),a[i].val=read();
    Kruskal();
    minai[1][0]=-inf;
    depth[1]=1;
    dfs(1,-1);
    cal();
    int ans=inf;
    fors(i,1,m){
        if(!vis[i]){
            int u=a[i].u,v=a[i].v,val=a[i].val;
            int lcas=lca(u,v),maxsu=querymax(u,lcas,val),maxsv=querymax(v,lcas,val);
            ans=min(ans,cnt-max(maxsu,maxsv)+val);
        }
    }
    printf("%lld\n",ans);
    return 0;
}
7 条评论
SEP · 2018年10月12日 5:54 下午
QAQ 居然回复我了。。。
SEP · 2018年10月10日 7:17 下午
黑书上有更方便的算法,可以参考 O(∩_∩)O
B_Z_B_Y · 2018年10月11日 8:34 下午
真的吗? 可以发给我看看吗? OvO 不过说起来黑书是啥?
XZYQvQ · 2018年10月11日 9:07 下午
黑书->算法艺术与信息学竞赛
B_Z_B_Y · 2018年10月12日 9:23 上午
Thank you !, 我竟然只找到了 pdf 版的 2333
SEP · 2018年10月12日 5:53 下午
算法导论(当时还是后面的思考题,官网有解答)
XZYQvQ · 2018年10月12日 6:33 下午
QvQ 我被我儿子坑了,我一开始一位是算导然而我 Naive 了 QwQ
(光速逃