Infinite Tree
题意
一颗无限结点的树,任意大于
1
1
1的点
k
k
k与点
k
m
i
n
d
i
v
(
k
)
\frac{k}{mindiv\left(k\right)}
mindiv(k)k相连,其中
m
i
n
d
i
v
(
k
)
mindiv\left(k\right)
mindiv(k)为
k
k
k的最小质因子
记
δ
(
u
,
v
)
\delta\left( u, v \right)
δ(u,v)为树上
u
−
v
u-v
u−v之间的距离,求
min
u
∑
i
=
1
m
w
i
δ
(
u
,
i
!
)
\min_u \displaystyle\sum_{i = 1} ^ {m} w_i \delta\left( u, i! \right)
minui=1∑mwiδ(u,i!)
题解
不考虑本题的树
我们先考虑这个题在已经知道树的结构下怎么解
显然
δ
(
u
,
v
)
=
d
i
s
(
u
,
v
)
\delta\left( u, v \right)=dis\left( u, v \right)
δ(u,v)=dis(u,v)
min
u
∑
i
=
1
m
w
i
δ
(
u
,
i
!
)
=
min
u
∑
i
=
1
m
w
i
d
i
s
(
u
,
i
!
)
\min_u \displaystyle\sum_{i = 1} ^ {m} w_i \delta\left( u, i! \right) = \min_u \displaystyle\sum_{i = 1} ^ {m} w_i dis\left( u, i! \right)
minui=1∑mwiδ(u,i!)=umini=1∑mwidis(u,i!)
在
r
o
o
t
=
1
root=1
root=1的树中,假设现在
u
=
1
u=1
u=1,那么当前答案
a
n
s
=
∑
i
=
1
m
w
i
d
i
s
(
1
,
i
!
)
ans=\displaystyle\sum_{i = 1} ^ {m} w_i dis\left( 1, i! \right)
ans=i=1∑mwidis(1,i!)
记
f
[
u
]
=
w
[
u
]
+
∑
v
∈
s
o
n
f
[
v
]
f[u]=w[u] + \displaystyle\sum_{v∈son}f[v]
f[u]=w[u]+v∈son∑f[v],那么在图中,显然
f
[
1
]
=
∑
i
=
1
m
w
i
f[1]=\displaystyle\sum_{i = 1} ^ {m} w_i
f[1]=i=1∑mwi
现在考虑当我们的点
u
u
u转移到
u
u
u的一个子节点
v
v
v时,答案会发生什么变化
那么就有
f
[
1
]
−
f
[
v
]
f[1]-f[v]
f[1]−f[v]多一段移动距离
d
i
s
(
u
,
v
)
dis\left( u,v \right)
dis(u,v),
f
[
v
]
f[v]
f[v]少一段移动距离
d
i
s
(
u
,
v
)
dis\left( u,v \right)
dis(u,v)
所以当我们转移
u
u
u点能够使答案变小的时候,即
(
f
[
1
]
−
f
[
v
]
)
−
(
f
[
v
]
)
=
f
[
1
]
−
2
f
[
v
]
<
0
(f[1]-f[v])-(f[v])=f[1]-2f[v]<0
(f[1]−f[v])−(f[v])=f[1]−2f[v]<0时,我们就会移动
u
u
u点,当不能继续移动时我们就找到了最终的答案
void dfs(int u, int fa) {//第一个dfs结束后,w就是子树的w之和,就是上面讲的f
f[u] = w[u]
for (auto &v: g[u])
if (v != fa) {
dfs(v, u);
f[u] += f[v];
}
}
void dfs2(int u, int fa) {//如果rt移动之后答案变小就一直移动下去,直到答案不在变小
for (auto &v: g[u])
if (v != fa) {
//rt从u转移到v的代价
//+(w[1] - w[v]) - w[v]
if (w[1] - 2 * w[v] < 0) {
ans += 1ll * (w[1] - 2 * w[v]) * (dep[v] - dep[u]);//一步的代价*距离
dfs2(v, u);
}
}
}
或者如果觉得这里路径不是单一的,可以用 d p dp dp数组来记录以每一个点为中心的答案,最后找到最小的就行
本题树的结构
接下来我们将本题构造的树画出来,下面是
m
=
6
m=6
m=6的时候建的树
因为任意大于
1
1
1的点
k
k
k与点
k
m
i
n
d
i
v
(
k
)
\frac{k}{mindiv\left(k\right)}
mindiv(k)k相连,所以这里我更直观的将
m
i
n
d
i
v
mindiv
mindiv标了出来,就是图上的边权
对于结点
i
i
i作质因数分解,记为
i
=
p
1
k
1
p
2
k
2
⋅
⋅
⋅
p
n
k
n
i=p_1^{k_1}p_2^{k_2}···p_n^{k_n}
i=p1k1p2k2⋅⋅⋅pnkn
这棵树从根节点
1
!
1!
1!到点
i
i
i的路径中,质因子由大变小,即经过的路径边上的质因数是形如
5
,
5
,
5
,
3
,
3
,
2
,
2
,
2
,
2
5,5,5,3,3,2,2,2,2
5,5,5,3,3,2,2,2,2
且有
d
i
s
(
i
,
1
!
)
=
∑
i
=
1
n
k
i
dis(i,1!)=\displaystyle\sum_{i=1}^{n}k_i
dis(i,1!)=i=1∑nki
而本题最大的点
m
!
,
1
≤
m
≤
1
e
5
m!, 1\leq m \leq1e5
m!,1≤m≤1e5是一个非常大的点,要将整棵树全部保存下来是不可能的
虚树
先来看看这个图(假设所有
w
i
=
1
w_i=1
wi=1,蓝色的字为结点的
f
f
f值)
除了绿色的点之外,其他所有的点的
f
f
f值都和他们的子节点相等!
也就是说,如果我们能够移动到点
v
v
v,即有
f
[
1
]
−
2
f
[
v
]
<
0
f[1]-2f[v]<0
f[1]−2f[v]<0,如果
f
[
v
v
]
=
f
[
v
]
f[vv]=f[v]
f[vv]=f[v]那么肯定会继续移动下去
所以这些
f
f
f值不变的点都是不重要的
只有我们的目标点
i
!
i!
i!和他们的最近公共祖先
l
c
a
lca
lca是有用的,这个就是虚树的概念
这样我们保留了绿色的点,新建的树就是这样:
现在考虑怎么建虚树
目标点
i
!
i!
i!好说,就是他们之间的
l
c
a
lca
lca比较难求
之前我们说了:
这棵树从根节点
1
!
1!
1!到点
i
i
i的路径中,质因子由大变小,即经过的路径边上的质因数是形如
5
,
5
,
5
,
3
,
3
,
2
,
2
,
2
,
2
5,5,5,3,3,2,2,2,2
5,5,5,3,3,2,2,2,2
所以当
i
!
=
p
1
k
1
p
2
k
2
⋅
⋅
⋅
p
n
k
n
i!=p_1^{k_1}p_2^{k_2}···p_n^{k_n}
i!=p1k1p2k2⋅⋅⋅pnkn,当变成
(
i
+
1
)
!
(i+1)!
(i+1)!时,这条路径最先改变的地方就是
(
i
+
1
)
(i+1)
(i+1)的最大质因子
如
2
4
3
2
5
3
2^43^25^3
243253乘
2
1
3
1
2^13^1
2131时
原来的路径:
5
,
5
,
5
,
3
,
3
,
2
,
2
,
2
,
2
5,5,5,3,3,2,2,2,2
5,5,5,3,3,2,2,2,2
现在的路径:
5
,
5
,
5
,
3
,
3
,
3
,
2
,
2
,
2
,
2
,
2
5,5,5,3,3,3,2,2,2,2,2
5,5,5,3,3,3,2,2,2,2,2
可以看到前面
5
5
5个数
5
,
5
,
5
,
3
,
3
5,5,5,3,3
5,5,5,3,3都一样的,也就是说在树上他们是公用这些节点的
那么
2
4
3
2
5
3
2^43^25^3
243253和
2
5
3
3
5
3
2^53^35^3
253353的最近公共祖先
l
c
a
lca
lca的深度就等于
5
5
5
可以得到
d
e
p
[
l
c
a
(
(
i
+
1
)
!
,
i
!
)
]
=
s
u
m
(
m
a
x
d
i
v
(
i
+
1
)
,
n
)
dep[lca((i+1)!,i!)]=sum(maxdiv(i+1), n)
dep[lca((i+1)!,i!)]=sum(maxdiv(i+1),n)
其中
s
u
m
(
m
a
x
d
i
v
(
i
+
1
)
,
n
)
sum(maxdiv(i+1), n)
sum(maxdiv(i+1),n)为原来
i
!
i!
i!中大于等于
m
a
x
d
i
v
(
i
+
1
)
maxdiv(i+1)
maxdiv(i+1)的因子个数
这样我们能够顺利找到两个相邻点之间的
l
c
a
lca
lca
为什么不用考虑所有点呢,因为构造这棵树的时候已经是按
d
f
s
dfs
dfs序排序了,所以考虑相邻的两个点即可
虚树的构造部分代码:
void buildVirtualTree() {
tot = n; st[top = 1] = 1;
for (int i = 2; i <= n; i++) {
dep[i] = dep[i - 1] + 1; int j = i;
for (; j != mindiv[j]; j /= mindiv[j]) dep[i]++;
//一般的树都是直接找lca的,但是这里是这样找的
//上面操作完成后,j = maxdiv(i)
lcadep[i] = query(n) - query(j - 1);//这一步就是查找sum(maxdiv(i), n)
//可以按照上面的图中的5!和6!两个点模拟一下
for (j = i; j != 1; j /= mindiv[j]) upd(mindiv[j], 1);
}
//下面和一般的虚树板子类似
for (int i = 2; i <= n; i++) {
while (top > 1 && dep[st[top - 1]] >= lcadep[i])
add_edge(st[top - 1], st[top]), top--;
if (dep[st[top]] != lcadep[i]) {
dep[++tot] = lcadep[i];
add_edge(tot, st[top]);
st[top] = tot;
}
st[++top] = i;
}
while (top > 1) add_edge(st[top - 1], st[top]), top--;
}
代码
#include<bits/stdc++.h>
#define lowbit(x) x&-x
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 10;
//建立虚树点数tot < 2n, 空间开两倍
int n, w[MAX];
ll ans;
//树状数组
int c[MAX];
void upd(int p, int k) { for (; p <= n; p += lowbit(p)) c[p] += k; }
int query(int p) { int res = 0; for (; p; p -= lowbit(p)) res += c[p]; return res; }
int mindiv[MAX];
void sieve(int siz) {//筛mindiv
for (int i = 2; i <= siz; i++)
if (!mindiv[i])
for (int j = i; j <= siz; j += i)
if (!mindiv[j])
mindiv[j] = i;
}
int lcadep[MAX], dep[MAX];
int st[MAX], top, tot;//stack, top, tot:虚树点数
vector<int> g[MAX];//虚树
void add_edge(int u, int v) { g[u].push_back(v), g[v].push_back(u); }
void buildVirtualTree() {
tot = n; st[top = 1] = 1;
for (int i = 2; i <= n; i++) {
dep[i] = dep[i - 1] + 1; int j = i;
for (; j != mindiv[j]; j /= mindiv[j]) dep[i]++;
lcadep[i] = query(n) - query(j - 1);
for (j = i; j != 1; j /= mindiv[j]) upd(mindiv[j], 1);
}
//建树
for (int i = 2; i <= n; i++) {
while (top > 1 && dep[st[top - 1]] >= lcadep[i])
add_edge(st[top - 1], st[top]), top--;
if (dep[st[top]] != lcadep[i]) {
dep[++tot] = lcadep[i];
add_edge(tot, st[top]);
st[top] = tot;
}
st[++top] = i;
}
while (top > 1) add_edge(st[top - 1], st[top]), top--;
}
void dfs(int u, int fa) {
ans += 1ll * w[u] * dep[u];//ans最开始是rt = 1时的答案
for (auto &v: g[u])
if (v != fa) {
dfs(v, u);
w[u] += w[v];
}
}
void dfs2(int u, int fa) {//如果rt移动之后答案变小就一直移动下去,直到答案不在变小
for (auto &v: g[u])
if (v != fa) {
//rt从u转移到v的代价
//+(w[1] - w[v]) - w[v]
if (w[1] - 2 * w[v] < 0) {
ans += 1ll * (w[1] - 2 * w[v]) * (dep[v] - dep[u]);//一步的代价*距离
dfs2(v, u);
}
}
}
void init() {
ans = top = 0;
for (int i = 1; i <= tot; i++) g[i].clear(), c[i] = w[i] = lcadep[i] = dep[i] = 0;
}
int main() {
sieve(1e5);
while (~scanf("%d", &n)) {
init();
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
buildVirtualTree();
int rt = 1; dfs(rt, 0); dfs2(rt, 0);
printf("%lld\n", ans);
}
return 0;
}