1 条题解

  • 0
    @ 2026-4-12 19:33:13

    G. 来自 Chelsu 的优化 题解

    问题描述

    给定一棵 nn 个节点的树,每条边有正整数权值 wiw_i1wi10121 \le w_i \le 10^{12})。定义:

    • len(u,v)\operatorname{len}(u,v)uuvv 简单路径上的边数。
    • gcd(u,v)\operatorname{gcd}(u,v):路径上所有边权的最大公约数(约定 gcd(u,u)=0\operatorname{gcd}(u,u)=0)。

    求 $\max\limits_{1\le u,v\le n} \operatorname{len}(u,v) \cdot \operatorname{gcd}(u,v)$。

    解法概述

    使用 重心分解,对每个重心处理所有经过该重心的路径,利用 gcd\gcd 的性质和剪枝枚举,在 O(nnlogn)O(n \sqrt{n} \log n) 时间内求解。

    关键观察

    1. 路径分解:经过重心 cc 的路径 (u,v)(u,v) 可分解为两段 (c,u)(c,u)(c,v)(c,v)。设:

      • g1=gcd(c,u)g_1 = \operatorname{gcd}(c,u)1=len(c,u)\ell_1 = \operatorname{len}(c,u)
      • g2=gcd(c,v)g_2 = \operatorname{gcd}(c,v)2=len(c,v)\ell_2 = \operatorname{len}(c,v) 则整条路径的 gcd=gcd(g1,g2)\gcd = \gcd(g_1, g_2),长度为 1+2\ell_1 + \ell_2。目标最大化 (1+2)gcd(g1,g2)(\ell_1 + \ell_2) \cdot \gcd(g_1, g_2)
    2. 12\ell_1 \ge \ell_2,则 gcd(g1,g2)=g1\gcd(g_1, g_2) = g_1
      证明:若 gcd(g1,g2)g1\gcd(g_1,g_2) \ne g_1,则它是 g1g_1 的真因数,至多为 g1/2g_1/2。但 g1g_1 本身是从 ccuugcd\gcd,往往较大。通过交换 u,vu,v,可以只考虑 gcd(g1,g2)=g1\gcd(g_1,g_2)=g_1 的情况。此时 g2g_2 必须是 g1g_1 的倍数,记 g2=g1kg_2 = g_1 \cdot kk1k \ge 1

    3. 剪枝:对于固定 (g1,1)(g_1, \ell_1),枚举 kk 时,若 g1(1+max)ansg_1 \cdot (\ell_1 + \ell_{\max}) \le ansmax\ell_{\max} 是已处理子树的最大深度),则即使取最大深度也无法更新答案,可提前终止。

    4. 枚举上界:由于 g2maxwi1012g_2 \le \max w_i \le 10^{12},且 k1k \le \ell_1(可从不等式推出),实际枚举量可控。复杂度可证明为 O(nnlogn)O(n \sqrt{n} \log n)

    算法步骤

    1. 重心分解

      • 计算子树大小,找到重心。
      • 处理以该重心为中间点的所有路径。
      • 删除重心,递归处理每个连通分量。
    2. 处理单个重心 cc

      • 维护 map<gcd, max_depth> 结构 best,初始包含 (0, 0)(重心自身)。
      • cc 的每个邻接子树(未删除):
        • 收集该子树内所有节点到 cc(gcd, depth) 对,存入 cur
        • 对于 cur 中的每个 (g, d)
          • 与重心自身组合:ans=max(ans,gd)ans = \max(ans, g \cdot d)
          • best 中已有的路径组合:枚举 gg 的倍数 g2=gkg_2 = g \cdot kk=1,2,k=1,2,\dots,直到 g2>1012g_2 > 10^{12} 或剪枝条件触发),若 best 中存在 g2g_2,则更新 ans=max(ans,g(d+best[g2]))ans = \max(ans, g \cdot (d + \text{best}[g_2]))
        • cur 合并到 best 中(保留每个 gcd\gcd 的最大深度),并更新 best_max
    3. 递归:删除重心后,对每个子树递归调用。

    复杂度分析

    • 重心分解深度 O(logn)O(\log n),每个节点被收集 O(logn)O(\log n) 次。
    • 对每个 (g, d),枚举 kk 的次数受 gg 的约数个数和剪枝限制,均摊 O(n)O(\sqrt{n}),故总复杂度 O(nnlogn)O(n \sqrt{n} \log n),可满足 n105n \le 10^5

    代码实现

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const int MAXN = 100005;
    const ll INF = 1e18;
    
    int n;
    vector<pair<int, ll>> adj[MAXN];
    bool dead[MAXN];
    int sz[MAXN];
    ll ans;
    
    // 计算子树大小
    void dfs_sz(int u, int p) {
        sz[u] = 1;
        for (auto [v, w] : adj[u]) {
            if (v == p || dead[v]) continue;
            dfs_sz(v, u);
            sz[u] += sz[v];
        }
    }
    
    // 寻找重心
    int find_centroid(int u, int p, int total) {
        for (auto [v, w] : adj[u]) {
            if (v == p || dead[v]) continue;
            if (sz[v] > total / 2) return find_centroid(v, u, total);
        }
        return u;
    }
    
    // 收集从重心出发到子树内所有节点的 (gcd, depth) 对
    void dfs_collect(int u, int p, ll cur_g, int depth, map<ll, int>& mp) {
        if (mp.count(cur_g)) mp[cur_g] = max(mp[cur_g], depth);
        else mp[cur_g] = depth;
        for (auto [v, w] : adj[u]) {
            if (v == p || dead[v]) continue;
            ll ng = __gcd(cur_g, w);
            dfs_collect(v, u, ng, depth + 1, mp);
        }
    }
    
    // 处理以 u 为重心的所有经过 u 的路径
    void process_centroid(int u) {
        map<ll, int> best;
        best[0] = 0;                // 重心自身
        int best_max = 0;
    
        for (auto [v, w] : adj[u]) {
            if (dead[v]) continue;
            map<ll, int> cur;
            dfs_collect(v, u, w, 1, cur);
    
            // 与之前子树组合
            for (auto [g, d] : cur) {
                // 与重心自身组合
                ans = max(ans, g * d);
                // 剪枝:若即使取最大深度也无法超过 ans,则跳过枚举
                if (g * (d + best_max) <= ans) continue;
                // 枚举 g 的倍数
                for (ll k = 1; ; ++k) {
                    ll g2 = g * k;
                    if (g2 > 1000000000000LL) break;   // 超过最大边权,不可能存在
                    auto it = best.find(g2);
                    if (it != best.end()) {
                        ans = max(ans, g * (d + it->second));
                    }
                    // 剪枝:若即使取最大深度也无法超过 ans,则后续更大的 k 也不可能
                    if (g * (d + best_max) <= ans) break;
                }
            }
    
            // 合并到 best
            for (auto [g, d] : cur) {
                if (best.count(g)) best[g] = max(best[g], d);
                else best[g] = d;
                best_max = max(best_max, best[g]);
            }
        }
    }
    
    // 重心分解
    void decompose(int u) {
        dfs_sz(u, -1);
        int c = find_centroid(u, -1, sz[u]);
        process_centroid(c);
        dead[c] = true;
        for (auto [v, w] : adj[c]) {
            if (!dead[v]) decompose(v);
        }
    }
    
    void solve() {
        cin >> n;
        for (int i = 1; i <= n; ++i) adj[i].clear();
        for (int i = 1; i < n; ++i) {
            int u, v; ll w;
            cin >> u >> v >> w;
            adj[u].push_back({v, w});
            adj[v].push_back({u, w});
        }
        ans = 0;
        memset(dead, 0, sizeof(dead));
        decompose(1);
        cout << ans << "\n";
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        int t;
        cin >> t;
        while (t--) solve();
        return 0;
    }
    

    总结

    本题通过重心分解将路径问题转化为多个子问题,利用 gcd\gcd 的倍数性质和深度剪枝,在可接受的时间内求得最优解。代码实现了上述算法,能够处理 n105n \le 10^5tt 较大的情况。

    • 1

    信息

    ID
    6498
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者