【集训】最小生成树!

未学习:Kruskal 重构树,可持久化并查集,Boruvka

最小生成树

P2330 [SCOI2005] 繁忙的都市

最小生成树 最小瓶颈树

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << " = " << x << endl
#define ll long long
typedef pair <int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
//#define int long long
inline int rd(){
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}
#define rd rd()

void wt(int x){
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        wt(x / 10);
    putchar(x % 10 + '0');
    return;
}
void wt(char x){
    putchar(x);
}
void wt(int x, char k){
    wt(x),putchar(k);
}

namespace Star_F{
    const int N = 100005;
    int fa[N], sum;
    int cnt;
    struct node{
        int u, v, w;
        friend bool operator<(node a,node b){
            return a.w < b.w;
        }
    }a[N];
    int find(int x){
        return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
    }
    void Main(){
        int n = rd, m = rd;
        FOR(i, 1, m)
            a[i].u = rd, a[i].v = rd, a[i].w = rd;
        FOR(i, 1, n) fa[i] = i;

        sort(a + 1, a + m + 1);
        FOR(i,1,m){
            int fu = find(a[i].u);
            int fv = find(a[i].v);
            if(fu==fv) continue;

            fa[fu] = fv;
            sum = a[i].w;
            cnt++;
            if(cnt==n-1)
                break;
        }
        cout << n - 1 << " " << sum << endl;
    }
}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ClockA;
    int T=1;
    // T=rd;
    while(T--) Star_F::Main();
    // ClockB;
    return 0;
}

P4047 [JSOI2010] 部落划分

假如 k=n1,怎么合并?就应该把最近的点对合并。

k=n2 呢?以此类推下去,其实就是从最近的点对开始,一对一对考虑,如果不在同一集合,则需要合并集合。

这个过程其实也就是 Kruskal 的过程,直接跑 Kruskal 到剩 k 个连通块时即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1050,M=1e6+100;
int n,k;
int fa[N];
struct node {
	int x,y;
	double v;
} p[M];
bool cmp(node a,node b) {
	return a.v<b.v;
}
int find(int x) {
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
void merge(int x,int y) {
	if(find(x)==find(y)) return;
	fa[fa[x]]=fa[y];
	return;
}
double x[N],y[N];
int main() {
	cin>>n>>k;
	for(int i=1; i<=n; i++) fa[i]=i;
	for(int i=1;i<=n;i++)
		cin>>x[i]>>y[i];
	int tot=0;
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			p[++tot]=(node){i,j,sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]))};
	sort(p+1,p+1+tot,cmp);
	int lft=n;
	double mx=0;
	for(int i=1; i<=tot; i++){
		int a=p[i].x,b=p[i].y;
		if(find(a)==find(b)) continue;
		merge(a,b);
		lft--;
		mx=p[i].v;
		if(lft<k) break;
	}
	printf("%.2lf\n",mx);
	return 0;
}

P2573 [SCOI2012] 滑雪

我们会发现不管我们往下怎么连边,都不会对高度比它高的产生影响,所以我们在给点排序应该以节点高度为第一关键字,距生成树的距离为第二关键字。而最小生成树的算法都是将能接到树上的节点全都接上,所以我们只需要在建树树统计一下就可以算出最多能到景点数量了。

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << " = " << x << endl
#define ll long long
typedef pair <int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
//#define int long long
inline int rd(){
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}
#define rd rd()

void wt(int x){
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        wt(x / 10);
    putchar(x % 10 + '0');
    return;
}
void wt(char x){
    putchar(x);
}
void wt(int x, char k){
    wt(x),putchar(k);
}

namespace Star_F{
    const int N = 200005, M = 2000005;
    int n, m, H[N];
    ll cnt, ans, dis[N], vis[N];
    int h[N], e[M], ne[M], w[M], idx;
    void add(int u,int v,int W){
        e[++idx] = v, ne[idx] = h[u], w[idx] = W, h[u] = idx;
    }
    struct node{
        int h, dis, id;
        bool friend operator<(node a,node b){
            if(a.h!=b.h)
                return a.h < b.h;
            return a.dis > b.dis;
        }
    };

    priority_queue<node> q;
    void prim(){
        memset(dis, 0x3f, sizeof(dis));
        dis[1] = 0;
        q.push({h[1], 0, 1});
        while(!q.empty()){
            int u = q.top().id;
            q.pop();
            if(vis[u])
                continue;
            vis[u] = 1;
            cnt++, ans += dis[u];
            for (int i = h[u]; i;i=ne[i]){
                int v = e[i];
                if(vis[v])
                    continue;
                if(dis[v]>w[i])
                    dis[v] = w[i], q.push({H[v], dis[v], v});
            }
        }
    }
    void Main(){
        cin >> n >> m;
        for (int i = 1; i <= n;i++)
            cin >> H[i];
        for (int i = 1; i <= m;i++){
            int u, v, w;
            cin >> u >> v >> w;
            if(H[u]>=H[v])
                add(u, v, w);
            if(H[v]>=H[u])
                add(v, u, w);
        }
        prim();
        cout << cnt << " "<< ans << endl;
    }

}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ClockA;
    int T=1;
    // T=rd;
    while(T--) Star_F::Main();
    // ClockB;
    return 0;
}

P8207 [THUPC 2022 初赛] 最小公倍树

Kruskal 优化建图

观察到如果直接建图的时间复杂度是 O((RL)2),这是无法接受的。但我们发现,在这个过程中,有很多边实际上是可以省略的,接下来我们来发掘这些边的性质。

首先,我们有

lcm(u,v)=u×vgcd(u,v)

这实际上启发了我们从 gcd(u,v) 的角度进行思考。我们发现,如果将两个有共同因子的点连边,这条边的权值是较小的。观察到 L,R106,这启发我们去枚举这个因子。同时我们发现,如果对于一个因子 x 来说,在 [L,R] 里的数都满足 kx,(k+1)x,,(k+p)x 这样的形式,显然对于 (k+1)x 及后面的数,向 kx 连边是最优的。这样我们对于一个因子最多建边数量为 RLx

而我们可以枚举因子,因子的范围是 [2,R],最后我们建边的数量就应该是

RL2+RL3++RLRRL2+RL3++RLR

根据调和级数,这个东西的边数是 O((RL)logR),实际上这个上界很松,具体数据会更小,极限数据大概是 105 左右的。

时间复杂度是 O((RL)log2(RL))

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << " = " << x << endl
#define ll long long
typedef pair <int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
//#define int long long
inline int rd(){
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}
#define rd rd()

void wt(int x){
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        wt(x / 10);
    putchar(x % 10 + '0');
    return;
}
void wt(char x){
    putchar(x);
}
void wt(int x, char k){
    wt(x),putchar(k);
}

namespace Star_F{
    const int N = 1000005;
    int l, r, f[N], buc[N];
    ll gcd(ll a, ll b){ return !b ? a : gcd(b, a % b); }
    ll lcm(ll a, ll b){ return a / gcd(a, b) * b;}

    int find(int x){
        return f[x] == x ? x : f[x] = find(f[x]);
    }
    struct node{
        int l, r;
        ll w;
        bool friend operator<(node a,node b){
            return a.w < b.w;
        }
    };
    vector<node> G;
    ll ans, cnt;
    void Kruskal(){
        sort(G.begin(), G.end());
        for(auto x:G){
            ll u = x.l, v = x.r, w = x.w;
            if(find(u)!=find(v)){
                f[max(find(u), find(v))] = min(find(u), find(v));
                ans += w;
            }
        }
    }
    void Main(){
        cin >> l >> r;
        for (int i = l; i <= r;i++)
            f[i] = i, buc[i] = 1;
        for (int i = 2; i <= r;i++){
            int tmp = 0;
            for (int j = i; j <= r;j+=i){
                if(buc[j]&&!tmp)
                    tmp = j;
                if(buc[j])
                    G.push_back({tmp, j, lcm(tmp, j)});
            }
            if(i>=l)
                G.push_back({tmp, l, lcm(tmp, l)});
        }
        Kruskal();
        cout << ans << endl;
    }

}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ClockA;
    int T=1;
    // T=rd;
    while(T--) Star_F::Main();
    // ClockB;
    return 0;
}

P3623 [APIO2008] 免费道路

优先加 0 权边,再加 1 权边,此时加入的 1 权边就是“必加”的 1 权边。再来一次,先将所有“必加”的 1 权边加入,再加入 1 权边直到 k 条,最后加 0 权边让图连通即可。

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << " = " << x << endl
#define ll long long
typedef pair <int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
//#define int long long
inline int rd(){
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}
#define rd rd()

void wt(int x){
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        wt(x / 10);
    putchar(x % 10 + '0');
    return;
}
void wt(char x){
    putchar(x);
}
void wt(int x, char k){
    wt(x),putchar(k);
}

namespace Star_F{
    const int N = 200005, M = 100005;
    int n, m, k, fa[N], tot, cnt;
    struct edge{
        int u, v, w;
    } e[M], ans[M];
    bool cmp1(edge a,edge b){
        return a.w > b.w;
    }
    bool cmp2(edge a,edge b){
        return a.w < b.w;
    }
    int find(int x){
        return fa[x] == x ? x : fa[x] = find(fa[x]);
    }
    bool merge(int x,int y){
        x = find(x),y = find(y);
        if(x==y)
            return false;
        fa[x] = y;
        return true;
    }
    void init(){
        cnt = tot = 0;
        for (int i = 1; i <= n;i++)
            fa[i] = i;
    }
    void check(){
        int tmp = find(1);
        for (int i = 2; i <= n;i++){
            int x = find(i);
            if(x!=tmp){
                cout << "no solution" << endl;
                exit(0);
            }
            tmp = x;
        }
    }
    void Main(){
        cin >> n >> m >> k;
        for (int i = 1; i <= m;i++)
            cin >> e[i].u >> e[i].v >> e[i].w;
        init();
        sort(e + 1, e + m + 1, cmp1);
        for (int i = 1; i <= m;i++)
            if(merge(e[i].u,e[i].v)&&e[i].w==0)
                tot++, e[i].w = -1;
        if(tot>k){
            cout << "no solution" << endl;
            exit(0);
        }
        check();
        init();
        sort(e + 1, e + m + 1, cmp2);
        for (int i = 1; i <= m;i++){
            int f1 = find(e[i].u), f2 = find(e[i].v);
            if(f1==f2)
                continue;
            if(e[i].w==1||tot<k){
                ans[++cnt] = e[i];
                fa[f1] = f2;
                if(e[i].w<1){
                    tot++;
                    e[i].w = 0;
                }
            }
        }
        if(tot<k){
            cout << "no solution" << endl;
            exit(0);
        }
        check();
        for (int i = 1; i <= cnt;i++){
            if(ans[i].w==-1)
                ans[i].w = 0;
            cout << ans[i].u << " " << ans[i].v << " " << ans[i].w << endl;
        }
    }
}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ClockA;
    int T=1;
    // T=rd;
    while(T--) Star_F::Main();
    // ClockB;
    return 0;
}

P4768 [NOI2018] 归程

Kruskal 重构树,可持久化并查集

CF1120D Power Tree

我们可以先找出这棵树的 dfs 序。dfs 序的特点是,相邻叶节点的 dfs 序总是连续的。

那对于每个点,我们就可以求出,它所能控制的叶节点对应的 dfn 序的一个区间。

这样就转化为一个区间问题了:

给定若干段区间 [l, r],每个区间有一个代价 a_i(对应原图的点权),还有一段 [1, n] 的序列 a。

控制一个区间,表示可以对区间里的数任意加减。

求出最少代价以及方案,不管 a_i 是多少,总能将 a 的所有数变为 0。

区间多次修改,单次查询,很容易想到差分。

每次相当于在差分数组 c 上执行:cl 加 x,cr+1x

并且显然有 1l,rn2r+1n+1(因为 lr 是 dfs 序)。

换句话说,我们所能更改的数是 c1cn+1,要想让所有 ci(1in)0,则所有数都要移到 cn+1 头上。

那么接下来就非常简单了:[l,r] 对应着一条 lr+1 的边,边权为 ai。求出最少边权和及方案,使得选择了这些边后,(n+1) 号点能与所有点连通。

然后跑一遍 kruskal 即可。事实上转化为区间这一步只是推导过程,实际代码只需要在求出区间 [l,r] 时,直接连边即可。

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << " = " << x << endl
#define ll long long
typedef pair <int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
//#define int long long
inline int rd(){
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}
#define rd rd()

void wt(int x){
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        wt(x / 10);
    putchar(x % 10 + '0');
    return;
}
void wt(char x){
    putchar(x);
}
void wt(int x, char k){
    wt(x),putchar(k);
}

namespace Star_F{
    const int N = 200005;
    int n, m, a[N];
    int fa[N];
    int h[N], idx;
    struct node{
        int u, v, w, pos;
    } e[N];
    void add1(int u,int v,int pos){
        e[++m].u = u, e[m].v = v;
        e[m].w = a[pos], e[m].pos = pos;
    }
    bool cmp(node a,node b){
        return a.w < b.w;
    }
    void init(){
        for (int i = 1; i <= n + 1;i++)
            fa[i] = i;
    }
    int find(int x){
        return fa[x] == x ? x : fa[x] = find(fa[x]);
    }
    struct edge{
        int u, nxt, w;
    } edge[N << 1];
    void add2(int u,int v){
        edge[++idx].u = v, edge[idx].nxt = h[u], h[u] = idx;
    }
    int dfn[N], dfnr[N];
    void dfs(int u,int fa){
        bool flag = 1;
        dfn[u] = 0x3f3f3f3f;
        for (int i = h[u]; i;i=edge[i].nxt){
            int v = edge[i].u;
            if(v==fa)
                continue;
            flag = 0, dfs(v, u);
            dfn[u] = min(dfn[u], dfn[v]), dfnr[u] = max(dfnr[u], dfnr[v]);
        }
        if(flag)
            dfn[u] = dfnr[u] = ++idx;
        add1(dfn[u], dfnr[u] + 1, u);
    }
    int tot;
    bool ans[N];
    ll Kruskal(){
        init();
        sort(e + 1, e + m + 1, cmp);
        ll sum = 0;
        for (int l = 1; l <= n;){
            int r;
            for (r = l; r + 1 <= n && e[r].w == e[r + 1].w;r++);
                
            for (int i = l; i <= r;i++)
                if(find(e[i].u)!=find(e[i].v))
                    ans[e[i].pos] = 1, tot++;
            for (int i = l; i <= r;i++){
                int x = find(e[i].u), y = find(e[i].v);
                if(x==y)
                    continue;
                fa[x] = y, sum += e[i].w;
            }
            l = r + 1;
        }
        return sum;
    }

    void Main(){
        cin >> n;
        for (int i = 1; i <= n;i++)
            cin >> a[i];
        for (int i = 1; i < n;i++){
            int u, v;
            cin >> u >> v;
            add2(u, v), add2(v, u);
        }
        idx = 0, dfs(1, 0);
        cout << Kruskal() << " " << tot << endl;
        for (int i = 1; i <= n;i++)
            if(ans[i])
                cout << i << " ";
    }

}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ClockA;
    int T=1;
    // T=rd;
    while(T--) Star_F::Main();
    // ClockB;
    return 0;
}

CF1550F Jumping Around

考虑对于每个结点 i 算出最小的满足条件的 k,记为 di

对于两个结点 u,v,如果 u 可以直接到 v,那么 k=|d|auav||。对于所有的 (u,v) 连一条 |d|auav|| 的边,那么从 si 的最小满足条件的 k 就是最小的 k,使得存在从 si 的路径且每条边的边权不大于 k

根据最小生成树的性质,实际上建出最小生成树后,di 就是从 si 的路径中最大的权值,这意味着我们需要找到这个最小生成树。

边数为 n2 级别的,直接使用 Kruskal 和 Prim 显然会超时,可以使用另外一种最小生成树算法:Boruvka。

这个东西就是维护每个联通块,然后对于每次迭代,对于每个联通块找到它连出去的最小的边,然后全部找完之后合并。因为每次联通块的次数会至少减半,所以迭代的次数是 logn 级别的。

虽然边数仍然是 n2 级别,但是对于联通块的每一个点 u,只要快速找到联通块以外最小的边,即 |d|auav|| 最小。

那么利用一个集合维护所有的点,对于每个联通块,先删去这个联通块中的所有点,然后再遍历所有点,用 lower\_bound 快速找到最接近于上面两个数的点,然后在遍历完成后连上最小的边就可以,然后就可以在 O(nlog2n) 的时间复杂度内找到最小生成树。

然后再按照上面所说的在这个树上进行 DFS 一次即可。

P4180 [BJWC2010] 严格次小生成树

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100010;
const ll INF = 1e18;
int n, m,d[N];  // n表示节点数,m表示边数,d[]是节点的深度
bool vis[N];  // vis[]标记节点是否已访问
ll f[N][25],g1[N][25],g2[N][25],mst, ans = INF;  // f[]是LCA的父节点,g1[]和g2[]存储两条最大边
struct Node {
	ll to,cost;  // 表示边的终点和权重
};
vector<Node> v[N];  // 邻接表存储图

// 深度优先遍历,构建树的深度和LCA所需要的数据
void dfs(const int x) {
	vis[x] = true;
	for (int i = 0; i < v[x].size(); i++) {
		int y = v[x][i].to;
		if (vis[y]) continue;
		d[y] = d[x] + 1;
		f[y][0] = x;  // 记录y的父节点
		g1[y][0] = v[x][i].cost;  // 记录边的权重
		g2[y][0] = -INF;  // 初始化第二大权重
		dfs(y);
	}
}

// 预处理LCA的父节点及最大最小边的相关信息
inline void prework() {
	for (int i = 1; i <= 20; i++)  // 最大深度是log(n),因此最多需要20层
		for (int j = 1; j <= n; j++) {
			f[j][i] = f[f[j][i - 1]][i - 1];  // 设置父节点
			g1[j][i] = max(g1[j][i - 1], g1[f[j][i - 1]][i - 1]);  // 记录最大边
			g2[j][i] = max(g2[j][i - 1], g2[f[j][i - 1]][i - 1]);  // 记录第二大边
			if (g1[j][i - 1] > g1[f[j][i - 1]][i - 1]) g2[j][i] = max(g2[j][i], g1[f[j][i - 1]][i - 1]);  // 若第一大边改变,第二大边可能更新
			else if (g1[j][i - 1] < g1[f[j][i - 1]][i - 1]) g2[j][i] = max(g2[j][i], g1[j][i - 1]);  // 若第二大边改变,也要更新
		}
}

// 计算两节点的LCA,并考虑次小生成树
inline void LCA(int x, int y, const ll w) {
	ll zui = -INF, ci = -INF;  // zui表示最大边权,ci表示第二大边权
	if (d[x] > d[y]) swap(x, y);  // 保证x的深度小于等于y
	for (int i = 20; i >= 0; i--)  // 将y上升到与x相同的深度
		if (d[f[y][i]] >= d[x]) {
			zui = max(zui, g1[y][i]);
			ci = max(ci, g2[y][i]);
			y = f[y][i];
		}
	if (x == y) {  // 如果x和y是同一个节点
		if (zui != w) ans = min(ans, mst - zui + w);  // 如果最大边不是w,更新答案
		else if (ci != w && ci > 0) ans = min(ans, mst - ci + w);  // 如果第二大边不是w,更新答案
		return;
	}
	for (int i = 20; i >= 0; i--)  // 如果x和y不同,找公共祖先
		if (f[x][i] != f[y][i]) {
			zui = max(zui, max(g1[x][i], g1[y][i]));  // 更新最大边权
			ci = max(ci, max(g2[x][i], g2[y][i]));  // 更新第二大边权
			x = f[x][i];
			y = f[y][i];
		}
	zui = max(zui, max(g1[x][0], g1[y][0]));  // 更新最终的最大边
	if (g1[x][0] != zui) ci = max(ci, g1[x][0]);  // 如果最大边不等于zui,更新第二大边
	if (g2[y][0] != zui) ci = max(ci, g2[y][0]);
	if (zui != w) ans = min(ans, mst - zui + w);  // 如果最大边不是w,更新答案
	else if (ci != w && ci > 0) ans = min(ans, mst - ci + w);  // 如果第二大边不是w,更新答案
}

struct Edge {
	int from, to;
	ll cost;
	bool is_tree;  // 标记边是否属于生成树
} edge[N * 3];

bool operator < (const Edge x, const Edge y) {
	return x.cost < y.cost;  // 根据边的权重排序
}

int fa[N];

// 并查集查找操作
inline int find(const int x) {
	if (fa[x] == x) return x;
	else return fa[x] = find(fa[x]);
}

// Kruskal算法求最小生成树
inline void Kruskal() {
	sort(edge, edge + m);  // 根据边的权重排序
	for (int i = 1; i <= n; i++) fa[i] = i;  // 初始化并查集
	for (int i = 0; i < m; i++) {
		int x = edge[i].from;
		int y = edge[i].to;
		ll z = edge[i].cost;
		int a = find(x), b = find(y);  // 查找x和y的根节点
		if (a == b) continue;  // 如果x和y已经连通,跳过
		fa[find(x)] = y;  // 合并两个连通分量
		mst += z;  // 更新最小生成树的权重
		edge[i].is_tree = true;  // 标记这条边属于生成树
		v[x].push_back((Node) {y, z});  // 将这条边加入邻接表
		v[y].push_back((Node) {x, z});  // 将这条边加入邻接表
	}
}

int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cin >> n >> m;  // 输入节点数和边数
	for (int i = 0, x, y; i < m; i++) {
		ll z;
		cin >> x >> y >> z;  // 输入边的两个端点和权重
		if (x == y) continue;  // 如果是自环,跳过
		edge[i].from = x;
		edge[i].to = y;
		edge[i].cost = z;
	}
	Kruskal();  // 求最小生成树
	d[1] = 1;
	dfs(1);  // 深度优先遍历,构建树的深度信息和LCA数据
	prework();  // 预处理LCA数据
	for (int i = 0; i < m; i++)  // 遍历所有非生成树边
		if (!edge[i].is_tree)
			LCA(edge[i].from, edge[i].to, edge[i].cost);  // 对每条非生成树边调用LCA
	cout << ans << "\n";  // 输出次小生成树的权重
	return 0;
}

posted @   Star_F  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示