codeforce 上有一个 m = 5000 m = 5000 m=5000 的版本
和式相当于求这棵树的关键点的带权重心,由于树是无限大的,不能用一般的方法去求关键点的重心。
注意到重心一定落在关键点上,而关键点只有 1 0 5 10^5 105 个,可以建立虚树,然后使用二次扫描换根法来求解重心。
建立虚树需要知道关键点的 dfs 序的大小关系。若在递归时,优先走质因子较小的树边,那么容易证明对于任意的 i ∈ [ 1 , n ) i \in[1,n) i∈[1,n),恒有 d f n [ ( i + 1 ) ! ] > d f n [ i ! ] dfn[(i + 1)!] > dfn[i!] dfn[(i+1)!]>dfn[i!] 成立。
证明如下:设 i ! i! i! 的质因子分解形式为: p 1 x 1 ∗ p 2 x 2 ∗ . . ∗ p n x n , ( p 1 < p 2 < . . < p n ) p_1^{x_1}*p_2^{x_2}*..*p_n^{x_n},(p_1 < p_2 < ..<p_n) p1x1∗p2x2∗..∗pnxn,(p1<p2<..<pn), ( i + 1 ) ! (i + 1)! (i+1)! 会在增大某几项质因子的幂次(也可能会产生一个新的质因子 p n + 1 p_{n+1} pn+1),设从右至左第一个幂次不等的质因子为 p i p_i pi,则在 p i x i p_i^{x_i} pixi走完以后, i ! i! i! 会走 p i − 1 p_{i - 1} pi−1 而 ( i + 1 ) ! (i + 1)! (i+1)! 会继续走 p i p_i pi,这使得 ( i + 1 ) ! (i + 1)! (i+1)! 的 dfs 序变大。当 ( i + 1 ) (i + 1) (i+1) 是一个质数时,第一个幂次不等的质因子为 p n + 1 p_{n + 1} pn+1,这种情况显然 dfs 序更小。
显然 d e p [ i ! ] = ∑ x i dep[i!] = \sum_{}x_i dep[i!]=∑xi,现在需要知道 d e p [ l c a ( i ! , ( i + 1 ) ! ) ] dep[lca(i!,(i + 1)!)] dep[lca(i!,(i+1)!)],根据定义它们的 l c a lca lca 显然为 p i x i ∗ p i + 1 x i + 1 ∗ . . . ∗ p n x n p_i^{x_i} * p_{i + 1}^{x_{i + 1}}*...*p_n^{x_n} pixi∗pi+1xi+1∗...∗pnxn,当 i + 1 i + 1 i+1 是一个质数时,他们的 l c a lca lca 为 1
构建虚树的过程,就是利用栈维护一条链,出现新的 lca,这个 lca 不会再和其它点再求 lca,因此只需要处理出 i ! i! i! 和 ( i + 1 ) ! (i + 1)! (i+1)! 的 l c a lca lca 的深度,这个过程可以利用树状数组得到, d e p [ ( i + 1 ) ! ] dep[(i+1)!] dep[(i+1)!] 可以由 d e p [ i ! ] dep[i!] dep[i!] + d e p [ i + 1 ] dep[i + 1] dep[i+1] 得到。
最后对构建完的虚树二次扫描换根求解重心
代码:
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
#define lowbit(i) (i & (-i))
typedef long long ll;
const int maxn = 3e5 + 10;
const int N = 1e5;
int w[maxn], n, dep[maxn], lcadep[maxn]; //dep 维护每个点的深度, lcadep 维护每个点和上一个点的 lca 的深度
int sta[maxn],top,tot,mindiv[maxn];
int sum[maxn], son[maxn];
ll val[maxn], ans[maxn];
vector<int> g[maxn];
void add(int u,int v) {
g[u].push_back(v);
//g[v].push_back(u);
}
int getsum(int x) {
int ans = 0;
for (int i = x; i; i -= lowbit(i))
ans += sum[i];
return ans;
}
void upd(int x,int v) {
for (int i = x; i <= N; i += lowbit(i))
sum[i] += v;
}
void prework() {
mindiv[1] = 0;
for (int i = 2; i <= 100000; i++)
for (int j = i; j <= 100000; j += i)
if (!mindiv[j]) mindiv[j] = i;
}
void build(int n) {
//建立虚数
//在这棵无限大的树,先遍历较小的质因子 ,则有 dfn[(i + 1)!] > dfn[i!]
dep[1] = sta[top = 1] = 1;
for (int i = 2, j; i <= n; i++) {
dep[i] = dep[i - 1] + 1; //加上最大质因子
for (j = i; j != mindiv[j]; j /= mindiv[j], dep[i]++);
lcadep[i] = getsum(n) - getsum(j - 1) + 1;
for (j = i; j != 1; j /= mindiv[j])
upd(mindiv[j],1);
}
for (int i = 2; i <= n; i++) {
while (top > 1 && dep[sta[top - 1]] >= lcadep[i]) {
add(sta[top - 1],sta[top]);
top--;
}
if (dep[sta[top]] != lcadep[i]) {
dep[++tot] = lcadep[i]; w[tot] = 0;
add(tot,sta[top]);
sta[top] = tot;
}
sta[++top] = i;
}
while (top > 1)
add(sta[top - 1],sta[top]), top--;
}
void dfs1(int u) {
val[u] = w[u]; ans[u] = 0;
for (auto it : g[u]) {
dfs1(it);
son[u] += son[it];
val[u] += val[it];
ans[u] += ans[it] + 1ll * val[it] * (dep[it] - dep[u]);
}
}
void dfs2(int u) {
for (auto it : g[u]) {
ll v = ans[u] - ans[it] - val[it] * (dep[it] - dep[u]);
ll s = val[u] - val[it];
ans[it] += v + s * (dep[it] - dep[u]);
val[it] += s;
dfs2(it);
}
}
void clear() {
for (int i = 2, j; i <= n; i++) {
for (j = i; j != 1; j /= mindiv[j])
upd(mindiv[j],-1);
}
}
int main() {
prework();
while (~scanf("%d",&n)) {
tot = n;
for (int i = 1; i <= n; i++)
scanf("%d",&w[i]);
build(n);
dfs1(1); dfs2(1);
ll res = 1e18;
for (int i = 1; i <= tot; i++)
res = min(res,ans[i]);
for (int i = 1; i <= tot; i++)
g[i].clear();
clear();
printf("%lld\n",res);
}
return 0;
}