1 条题解
-
0
G. 来自 Chelsu 的优化 题解
问题描述
给定一棵 个节点的树,每条边有正整数权值 ()。定义:
- : 到 简单路径上的边数。
- :路径上所有边权的最大公约数(约定 )。
求 $\max\limits_{1\le u,v\le n} \operatorname{len}(u,v) \cdot \operatorname{gcd}(u,v)$。
解法概述
使用 重心分解,对每个重心处理所有经过该重心的路径,利用 的性质和剪枝枚举,在 时间内求解。
关键观察
-
路径分解:经过重心 的路径 可分解为两段 和 。设:
- ,
- , 则整条路径的 ,长度为 。目标最大化 。
-
若 ,则
证明:若 ,则它是 的真因数,至多为 。但 本身是从 到 的 ,往往较大。通过交换 ,可以只考虑 的情况。此时 必须是 的倍数,记 ,。 -
剪枝:对于固定 ,枚举 时,若 ( 是已处理子树的最大深度),则即使取最大深度也无法更新答案,可提前终止。
-
枚举上界:由于 ,且 (可从不等式推出),实际枚举量可控。复杂度可证明为 。
算法步骤
-
重心分解:
- 计算子树大小,找到重心。
- 处理以该重心为中间点的所有路径。
- 删除重心,递归处理每个连通分量。
-
处理单个重心 :
- 维护
map<gcd, max_depth>结构best,初始包含(0, 0)(重心自身)。 - 对 的每个邻接子树(未删除):
- 收集该子树内所有节点到 的
(gcd, depth)对,存入cur。 - 对于
cur中的每个(g, d):- 与重心自身组合:。
- 与
best中已有的路径组合:枚举 的倍数 (,直到 或剪枝条件触发),若best中存在 ,则更新 。
- 将
cur合并到best中(保留每个 的最大深度),并更新best_max。
- 收集该子树内所有节点到 的
- 维护
-
递归:删除重心后,对每个子树递归调用。
复杂度分析
- 重心分解深度 ,每个节点被收集 次。
- 对每个
(g, d),枚举 的次数受 的约数个数和剪枝限制,均摊 ,故总复杂度 ,可满足 。
代码实现
#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; }总结
本题通过重心分解将路径问题转化为多个子问题,利用 的倍数性质和深度剪枝,在可接受的时间内求得最优解。代码实现了上述算法,能够处理 且 较大的情况。
- 1
信息
- ID
- 6498
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者