信息学奥赛一本通1713:生成树

题目描述】
给定一张N个点M条边的无向图,求图中所有生成树边权最大公约数的最小公倍数。

【输入】
第一行有两个整数N,M,分别表示点数和边数。

接下来M行每行有三个整数si,ti,di,表示点si与点ti间存在一条权为di的边,保证si不等于ti。

【输出】
所求的最小公倍数ans。

输入样例】

3 3
1 2 2
2 3 3
1 3 6

【输出样例】

6

\(N≤1000,M≤100000,di≤2^{15}−1,ans≤2^{64}−1\)

这题题干很优雅啊!

首先,我们直接做发现很难,那就可以通过贡献的方法解决。

具体的,我们可以对ans质因数分解,变成\(∏p_{i}^{a_{i}}\)的形式,因为他是一个lcm的值,所以他的所有质因数,都是由所有边权的质因数贡献的

那么就好办了,直接计算每个\(p_{i}\)的贡献,也就是\(a_{i}\),最后一乘不就完事了吗?而且还自动地把可能会重复计数的问解决了,那么问题就只有一个了,怎么计算贡献。

因为我们只需要计算\(p_{i}\)的贡献,那么我们其实可以当其他数的都不存在,只有\(p\)的幂。这时,你要算最大公因数,既然都是最大的了,不就是找一个生成树,然后让他的最小值最大吗?

这里虽然很像二分,但是其实只需要排个序,然后从大到小加边,如果某一个时刻整个图联通了,那么这个边的边权就是最大的最小边。

复杂度如果有P个质数,那么就是\(O(PM+MlogM)\)

code:

#include <iostream>
#include <algorithm>
#include <cstring>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
const int N = 1001;
const int M = 100001;
const int INF = 0x3f3f3f3f;

int read() {
    int k = 0, f = 1;char c = getchar();
    for (;c < '0' || c>'9';c = getchar())if (c == '-')f = -1;
    for (;c >= '0' && c <= '9';c = getchar())k = (k << 1) + (k << 3) + (c ^ 48);
    return k * f;
}

struct edge {
    int u, v, w;
    bool operator<(const edge& a)const {
        return w < a.w;
    }
}edges[M];

bool is_p[1 << 16];
vector<int>prim;
int cnt[1 << 16];
int n, m;
bool vis[N];
vector<edge>E[1 << 16];

void init() {
    memset(is_p, true, sizeof(is_p));
    is_p[0] = is_p[1] = false;
    for (int i = 2;i < (1 << 15);i++) {
        if (is_p[i])prim.push_back(i);
        for (int p : prim) {
            if (p * i > (1 << 15))break;
            is_p[p * i] = false;
            if (i % p == 0)break;
        }
    }
}

int fast_pow(int a, int b) {
    int ret = 1;
    while (b) {
        if (b & 1)ret *= a;
        a *= a;
        b >>= 1;
    }
    return ret;
}

signed main() {
    init();
    n = read(), m = read();
    for (int i = 0;i < m;i++) {
        edges[i].u = read(), edges[i].v = read(), edges[i].w = read();
        int temp = edges[i].w;
        for (int p : prim) {
            if (p*p > temp)break;//一定要加,不然会超时
            if (temp % p == 0) {
                int cnt2 = 0;
                cnt[p]++;
                while (temp % p == 0)cnt2++, temp /= p;
                E[p].push_back({ edges[i].u,edges[i].v,cnt2 });
            }
        }
        if (temp > 1)cnt[temp]++, E[temp].push_back({ edges[i].u,edges[i].v,1 });
    }
    for (int p : prim) {
        if (cnt[p] < n - 1)continue;
        sort(E[p].begin(), E[p].end());
    }
    int ans = 1;
    for (int p : prim) {
        if (cnt[p] < n - 1)continue;//枝剪,加不加都行
        memset(vis, false, sizeof(vis));
        int cnt2 = 0, i = E[p].size() - 1;
        for (;i >= 0;i--) {
            if (!vis[E[p][i].u])cnt2++, vis[E[p][i].u] = true;
            if (!vis[E[p][i].v])cnt2++, vis[E[p][i].v] = true;
            if (cnt2 == n)break;
        }
        if (cnt2 < n)continue;
        ans *= fast_pow(p, E[p][i].w);
    }
    cout << ans;
}
posted @ 2026-09-10 21:53  DK_orz  阅读(7)  评论(0)    收藏  举报