Infinite Tree
题意
一颗无限结点的树,任意大于
的点
与点
相连,其中
为
的最小质因子
记
为树上
之间的距离,求
题解
不考虑本题的树
我们先考虑这个题在已经知道树的结构下怎么解
显然
在
的树中,假设现在
,那么当前答案
记
,那么在图中,显然
现在考虑当我们的点
转移到
的一个子节点
时,答案会发生什么变化
那么就有
多一段移动距离
,
少一段移动距离
所以当我们转移
点能够使答案变小的时候,即
时,我们就会移动
点,当不能继续移动时我们就找到了最终的答案
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);
}
}
}
或者如果觉得这里路径不是单一的,可以用 数组来记录以每一个点为中心的答案,最后找到最小的就行
本题树的结构
接下来我们将本题构造的树画出来,下面是
的时候建的树
因为任意大于
的点
与点
相连,所以这里我更直观的将
标了出来,就是图上的边权
对于结点
作质因数分解,记为
这棵树从根节点
到点
的路径中,质因子由大变小,即经过的路径边上的质因数是形如
且有
而本题最大的点
是一个非常大的点,要将整棵树全部保存下来是不可能的
虚树
先来看看这个图(假设所有
,蓝色的字为结点的
值)
除了绿色的点之外,其他所有的点的
值都和他们的子节点相等!
也就是说,如果我们能够移动到点
,即有
,如果
那么肯定会继续移动下去
所以这些
值不变的点都是不重要的
只有我们的目标点
和他们的最近公共祖先
是有用的,这个就是虚树的概念
这样我们保留了绿色的点,新建的树就是这样:
现在考虑怎么建虚树
目标点
好说,就是他们之间的
比较难求
之前我们说了:
这棵树从根节点
到点
的路径中,质因子由大变小,即经过的路径边上的质因数是形如
所以当
,当变成
时,这条路径最先改变的地方就是
的最大质因子
如
乘
时
原来的路径:
现在的路径:
所以
这样我们能够顺利找到两个相邻点之间的
为什么不用考虑所有点呢,因为构造这棵树的时候已经是按
序排序了,所以考虑相邻的两个点即可
虚树的构造部分代码:
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;
}