UOJ33 [UR#2] 树上 GCD

简要题意: 给定一棵有根树,对于每个 \(i \in [1,n)\),求出下式的值:

\[Ans[i] = \sum_{u<v} \gcd({\rm{dis}}(u,{\rm{LCA}}(u,v)),{\rm{dis}}(v,{\rm{LCA}}(u,v)))
\]

特别地,\(\gcd(x,0) = \gcd(0,x) = x \ (x \neq 0)\)。

数据规模: \(n \le 2 \times 10^5\)。

题解: 对于一类求 \(\sum[\gcd(x,y) = i]\) 的数量的题目,常用的解法是先求出 \(\sum[i \mid \gcd(x,y)]\),然后通过枚举 \(i\) 的倍数进行容斥,得到 \(\gcd\) 恰好为 \(i\) 的数量,其本质就是狄利克雷后缀差分(我瞎起的名字)。而 \(\gcd\) 为 \(i\) 的倍数的答案,容易知道就是 \(\sum [(i \mid x) \ \land \ (i \mid y)]\),这可以通过合并 \(x\) 和 \(y\) 所在的 \(cnt\) 数组轻易求得。

回到本题上。套路地,我们考虑在 \({\rm{LCA}}(u,v)\) 处计算出点对 \((u,v)\) 对答案的贡献。比较 trivial 的做法是维护 \(cnt[u][d]\) 表示 \(u\) 的子树中到 \(u\) 的距离为 \(d\) 的点的数量,容易通过启发式合并维护,使用类似长链剖分的方式分析一下发现是 \(O(n)\) 的。

接下来,考虑在合并链的过程中,通过 \(cnt\) 数组求出答案。令 \(len[u]\) 表示 \(u\) 的子树内距离 \(u\) 的最大值。假设我们合并 \(cnt[u]\) 以及 \(cnt[v]\),其中 \(len[u] \ge len[v]\),显然这两条链只会对 \(i \le len[v]\) 的答案作出贡献。因此,可以枚举 \(i \in [1,len[v]]\),此时的复杂度显然为 \(O(n)\)。再考虑如何计算出 \(\sum[(i \mid x) \ \land \ (i \mid y)]\),对于较短的链 \(v\),直接暴力做 \(O(n \ln n)\) 的狄利克雷后缀和,于是现在问题在于不能对长链 \(u\) 暴力计算。

仍然是套路,不妨考虑对 \(i\) 进行根号分治。显然 \(i > \sqrt n\) 的部分,暴力在 \(cnt[u]\) 上跳 \(i\) 的倍数,复杂度是可以接受的 \(O(n \sqrt n)\)。而对于 \(i \le \sqrt n\),由于数量较少,则考虑直接维护。只需对于每个 \(i\),分别计算一遍,维护出 \(siz[u]\) 表示 \(u\) 的子树中距离 \(u\) 为 \(i\) 的倍数的数量,可以通过 \(u\) 向 \(u\) 的 \(i\) 级祖先更新以快速地维护。显然这部分的复杂度也是 \(O(n \sqrt n)\) 的,完全可以接受。

最后考虑一种特殊情况,即对于 \(u\) 是 \(v\) 的祖先的部分,由于在启发式合并时可能会交换 \(cnt[u]\) 与 \(cnt[v]\),故这种情况的贡献不能在合并过程中统计。但是也很好维护,因为注意到所有深度 \(\ge i\) 的点(深度指到根的距离)都会对 \(Ans[i]\) 造成 \(1\) 的贡献,所以只需要在输出答案时加上即可。

综上,整个程序的复杂度为 \(O(n \sqrt n)\),期望得分 \(100\)。

代码:

using ll = long long;
const int N = 2e5 + 10;
// kfa[u] 为 u 的 i 级祖先
// cnt[u] 为 u 的子树内深度为 i 的倍数 -1 的数量
// siz[u] 为 u 的子树内深度恰好为 i 的倍数的数量
int n, blk, fa[N], kfa[N], dep[N], cnt[N], siz[N], num[N];
ll ans[N];
vector<int> vec[N]; // cnt[u][d] 反过来储存
signed main() {
read(n);
for (int i = 2; i <= n; ++i)
read(fa[i]), dep[i] = dep[fa[i]] + 1, ++num[dep[i]];
for (int i = n; i >= 1; --i)
num[i] += num[i + 1];
iota(kfa + 1, kfa + n + 1, 1);
blk = ceil(sqrt(n));
for (int i = 1; i <= blk; ++i) {
fill(cnt + 1, cnt + n + 1, 0);
fill(siz + 1, siz + n + 1, 0);
for (int j = n; j >= 2; --j) {
cnt[kfa[j]] += siz[j] + 1;
ans[i] += 1ll * siz[fa[j]] * cnt[j];
siz[fa[j]] += cnt[j];
}
for (int j = 1; j <= n; ++j)
kfa[j] = fa[kfa[j]];
}
for (int i = n; i >= 2; --i) {
vec[i].push_back(1);
if (vec[i].size() > vec[fa[i]].size())
vec[i].swap(vec[fa[i]]);
int sz = vec[i].size(), szf = vec[fa[i]].size();
for (int j = blk + 1; j <= sz; ++j) {
int cnt1 = 0, cnt2 = 0;
for (int k = j; k <= sz; k += j) cnt1 += vec[i][sz - k];
for (int k = j; k <= szf; k += j) cnt2 += vec[fa[i]][szf - k];
ans[j] += 1ll * cnt1 * cnt2;
}
for (int j = 1; j <= sz; ++j)
vec[fa[i]][szf - j] += vec[i][sz - j];
}
for (int i = n - 1; i >= 1; --i)
for (int j = i + i; j < n; j += i)
ans[i] -= ans[j];
for (int i = 1; i < n; ++i)
write(ans[i] + num[i]), putchar('\n');
return 0;
}

总结: 这样一道题,不论是计算 \(\gcd\) 时的容斥,还是在 \(\rm LCA\) 处启发式合并时计算贡献,又或是在求 \(i\) 的倍数的数量时进行的根号分治,实际上都是非常套路、非常“板”的东西,一步步推下来思路是很顺畅的。相信各位选手们在仔细分析过此题后,一定能够很快地得到正解吧!没办法,谁叫C让我写题解呢

UOJ33 [UR#2] 树上 GCD的更多相关文章

  1. UOJ33 [UR #2] 树上GCD 【点分治】【容斥原理】【分块】

    题目分析: 树上点对问题首先想到点分治.假设我们进行了点分治并递归地解决了子问题.现在我们合并问题. 我们需要找到所有经过当前重心$ c $的子树路径.第一种情况是LCA为当前重心$ c $.考虑以$ ...

  2. 【uoj33】 UR #2—树上GCD

    http://uoj.ac/problem/33 (题目链接) 题意 给出一棵${n}$个节点的有根树,${f_{u,v}=gcd(dis(u,lca(u,v)),dis(v,lca(u,v)))}$ ...

  3. [UOJ UR #2]树上GCD

    来自FallDream的博客,未经允许,请勿转载,谢谢. 传送门 看完题目,一般人都能想到 容斥稳了 .这样我们只要统计有多少点对满足gcd是i的倍数. 考虑长链剖分,每次合并的时候,假设我已经求出轻 ...

  4. 【UOJ#33】【UR#2】树上GCD 有根树点分治 + 容斥原理 + 分块

    #33. [UR #2]树上GCD 有一棵$n$个结点的有根树$T$.结点编号为$1…n$,其中根结点为$1$. 树上每条边的长度为$1$.我们用$d(x,y)$表示结点$x,y$在树上的距离,$LC ...

  5. 【UOJ#33】【UR #2】树上GCD(长链剖分,分块)

    [UOJ#33][UR #2]树上GCD(长链剖分,分块) 题面 UOJ 题解 首先不求恰好,改为求\(i\)的倍数的个数,最后容斥一下就可以解决了. 那么我们考虑枚举一个\(LCA\)位置,在其两棵 ...

  6. uoj33 【UR #2】树上GCD

    题目 大致是长剖+\(\rm dsu\ on\ tree\)的思想 先做一个转化,改为对于\(i\in[1,n-1]\)求出有多少个\(f(u,v)\)满足\(i|f(u,v)\),这样我们最后再做一 ...

  7. [UOJ]#33. 【UR #2】树上GCD

    题目大意:给定一棵有根树,边长均为1,对于每一个i,求树上有多少个点对,他们到lca距离的gcd是i.(n<=200,000) 做法:先容斥,求出gcd是i的倍数的点对,考虑长链剖分后从小到大合 ...

  8. 【UR #2】树上GCD

    这道题是有根树点分治+烧脑的容斥+神奇的分块 因为是规定1为根,还要求LCA,所以我们不能像在无根树上那样随便浪了,必须规定父亲,并作特殊讨论 因为gcd并不好求,所以我们用容斥转化一下,求x为gcd ...

  9. UOJ#33. 【UR #2】树上GCD 点分治 莫比乌斯反演

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ33.html 题解 首先我们把问题转化成处理一个数组 ans ,其中 ans[i] 表示 d(u,a) 和 ...

随机推荐

  1. oneplus8手机蓝牙连接tws耳机无法双击退出语音助手

    通过蓝牙协议栈我们知道,蓝牙耳机可以通过发送AT指令唤醒或者退出语音助手 唤醒语音助手: AT+BVRA=1 退出语音助手: AT+BVRA=0 但是实际操作中发现双击可以唤醒但再次双击却无法退出语音 ...

  2. Oracle安装和卸载

    Oracle安装: 1. 检查是否安装net framework 3.5 2. 安装win64_11gR2_database服务端 更改安装目录,设置密码 2. 检查服务 services.msc,两 ...

  3. Activiti7基本介绍

    官方地址 官方地址 官方最新用户文档-V6.0.0 码云镜像-activiti-7-developers-guide 关于BPMN BPMN(Business Process Model AndNot ...

  4. Hudi 数据湖的插入,更新,查询,分析操作示例

    Hudi 数据湖的插入,更新,查询,分析操作示例 作者:Grey 原文地址: 博客园:Hudi 数据湖的插入,更新,查询,分析操作示例 CSDN:Hudi 数据湖的插入,更新,查询,分析操作示例 前置 ...

  5. Vue3 SFC 和 TSX 方式调用子组件中的函数

    在开发中会遇到这样的需求:获取子组件的引用,并调用子组件中定义的方法.如封装了一个表单组件,在父组件中需要调用这个表单组件的引用,并调用这个表单组件的校验表单函数或重置表单函数.要实现这个功能,首先要 ...

  6. 京东云开发者|经典同态加密算法Paillier解读 - 原理、实现和应用

    摘要 随着云计算和人工智能的兴起,如何安全有效地利用数据,对持有大量数字资产的企业来说至关重要.同态加密,是解决云计算和分布式机器学习中数据安全问题的关键技术,也是隐私计算中,横跨多方安全计算,联邦学 ...

  7. Pyserial 学习

    # 连接串口 ser = serial.Serial("COM12", 115200, timeout=0.5, bytesize=8, parity=serial.PARITY_ ...

  8. Oracle数据库的导出和导入

    本次数据库的导入导出操作是导出公司环境的Oracle数据库,再导入本地数据库,采用impdp和expdp命令进行导入导出操作. 一.导出52数据库 1.用system用户登录到数据库,查看是否有创建d ...

  9. CSS动画-transition/animation

    HTML系列: 人人都懂的HTML基础知识-HTML教程(1) HTML元素大全(1) HTML元素大全(2)-表单 CSS系列: CSS基础知识筑基 常用CSS样式属性 CSS选择器大全48式 CS ...

  10. WPF之MVVM实践中的Command与CommandParameter

    先记录一下,方便以后复习. https://www.cnblogs.com/babietongtianta/p/3474101.html