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; }