题意
令连接 u , v u,v u,v 的边的边权为 lcm ( u , v ) \text{lcm}(u,v) lcm(u,v)。给定 L , R L,R L,R,求 [ L , R ] [L,R] [L,R] 内的点所构成的无向图的最小生成树中边的边权和。
L , R ≤ 1 0 6 , R − L ≤ 1 0 5 L,R\leq 10^6,R-L\leq10^5 L,R≤106,R−L≤105。
解法
直接 O ( n 2 ) O(n^2) O(n2) 两两建边显然不行,考虑优化建边。
对于两个点 u , v u,v u,v, ( u , v ) (u,v) (u,v) 的边权为 lcm ( u , v ) = u × v gcd ( u , v ) \text{lcm}(u,v)=\cfrac{u\times v}{\gcd(u,v)} lcm(u,v)=gcd(u,v)u×v,显然应该选择 gcd ( u , v ) \gcd(u,v) gcd(u,v) 尽可能大的点对连边,也就是说 u , v u,v u,v 的公因子越多越好。
因为 L , R ≤ 1 0 6 L,R\leq10^6 L,R≤106,于是考虑枚举这个公因子。对于一个因子 x x x,如果 k x kx kx 在 [ L , R ] [L,R] [L,R] 中,则 ( k + 1 ) x , ( k + 2 ) x , … (k+1)x,(k+2)x,\dots (k+1)x,(k+2)x,… 这些点向 k x kx kx 连边是较优的。所以我们可以找到这个最小的 k x kx kx 作为起点连边。最后跑最小生成树即可。
实现
枚举的因子范围是 [ 2 , R ] [2,R] [2,R] 的,对于一个因子 x x x,在发现第一个 L ≤ k x ≤ R L\leq kx\leq R L≤kx≤R 的时候将 k x kx kx 标记为起点,向之后在 [ L , R ] [L,R] [L,R] 内的 ( k + 1 ) x , ( k + 2 ) x , … (k+1)x,(k+2)x,\dots (k+1)x,(k+2)x,… 连边。为确保连通,还需要连一条 ( L , k x ) (L,kx) (L,kx) 的边。
设点数为
n
n
n,边数为
m
m
m,根据调和级数,最后边的数量
m
m
m 大约是
O
(
n
log
n
)
O(n\log n)
O(nlogn) 级别的,Kruskal
最小生成树
O
(
m
log
m
)
O(m\log m)
O(mlogm),总时间复杂度大约在
O
(
n
log
2
n
)
O(n\log^2n)
O(nlog2n) 左右。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e6 + 5;
int L,R;
struct Edge {
int u,v,w;
};
vector<Edge> e;
int fa[maxn];
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
signed main() {
scanf("%lld%lld",&L,&R);
for (int i = 2,st = 0;i <= R;i ++,st = 0) {
for (int j = i;j <= R;j += i)
if (j >= L) {
if (st == 0) st = j;
e.push_back(Edge{st,j,st / __gcd(st,j) * j});
}
if (i >= L)
e.push_back(Edge{st,L,st / __gcd(st,L) * L});
}
for (int i = L;i <= R;i ++) fa[i] = i;
sort(e.begin(),e.end(),[&](const Edge &x,const Edge &y) {
return x.w < y.w;
});
int ans = 0;
for (auto E : e) {
int u = find(E.u), v = find(E.v), w = E.w;
if (u == v) continue;
ans += w, fa[u] = v;
}
printf("%lld",ans);
}