UOJ33 [UR#2] 树上 GCD
UOJ33 [UR#2] 树上 GCD
简要题意: 给定一棵有根树,对于每个 \(i \in [1,n)\),求出下式的值:
\]
特别地,\(\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的更多相关文章
- UOJ33 [UR #2] 树上GCD 【点分治】【容斥原理】【分块】
题目分析: 树上点对问题首先想到点分治.假设我们进行了点分治并递归地解决了子问题.现在我们合并问题. 我们需要找到所有经过当前重心$ c $的子树路径.第一种情况是LCA为当前重心$ c $.考虑以$ ...
- 【uoj33】 UR #2—树上GCD
http://uoj.ac/problem/33 (题目链接) 题意 给出一棵${n}$个节点的有根树,${f_{u,v}=gcd(dis(u,lca(u,v)),dis(v,lca(u,v)))}$ ...
- [UOJ UR #2]树上GCD
来自FallDream的博客,未经允许,请勿转载,谢谢. 传送门 看完题目,一般人都能想到 容斥稳了 .这样我们只要统计有多少点对满足gcd是i的倍数. 考虑长链剖分,每次合并的时候,假设我已经求出轻 ...
- 【UOJ#33】【UR#2】树上GCD 有根树点分治 + 容斥原理 + 分块
#33. [UR #2]树上GCD 有一棵$n$个结点的有根树$T$.结点编号为$1…n$,其中根结点为$1$. 树上每条边的长度为$1$.我们用$d(x,y)$表示结点$x,y$在树上的距离,$LC ...
- 【UOJ#33】【UR #2】树上GCD(长链剖分,分块)
[UOJ#33][UR #2]树上GCD(长链剖分,分块) 题面 UOJ 题解 首先不求恰好,改为求\(i\)的倍数的个数,最后容斥一下就可以解决了. 那么我们考虑枚举一个\(LCA\)位置,在其两棵 ...
- uoj33 【UR #2】树上GCD
题目 大致是长剖+\(\rm dsu\ on\ tree\)的思想 先做一个转化,改为对于\(i\in[1,n-1]\)求出有多少个\(f(u,v)\)满足\(i|f(u,v)\),这样我们最后再做一 ...
- [UOJ]#33. 【UR #2】树上GCD
题目大意:给定一棵有根树,边长均为1,对于每一个i,求树上有多少个点对,他们到lca距离的gcd是i.(n<=200,000) 做法:先容斥,求出gcd是i的倍数的点对,考虑长链剖分后从小到大合 ...
- 【UR #2】树上GCD
这道题是有根树点分治+烧脑的容斥+神奇的分块 因为是规定1为根,还要求LCA,所以我们不能像在无根树上那样随便浪了,必须规定父亲,并作特殊讨论 因为gcd并不好求,所以我们用容斥转化一下,求x为gcd ...
- UOJ#33. 【UR #2】树上GCD 点分治 莫比乌斯反演
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ33.html 题解 首先我们把问题转化成处理一个数组 ans ,其中 ans[i] 表示 d(u,a) 和 ...
随机推荐
- esp32把玩记-④ 星星点灯 (点亮led)
注意 全程使用Micropython,不会安装看我第一篇文章感谢 正式开始 用Thonny烧录(运行)以下代码 import time from machine import Pin led=Pin( ...
- 线性表的基本操作(C语言实现)
文章目录 这里使用的工具是DEV C++ 可以借鉴一下 实现效果 顺序存储代码实现 链式存储存储实现 这里使用的工具是DEV C++ 可以借鉴一下 一.实训名称 线性表的基本操作 二.实训目的 1.掌 ...
- 2、第二种传输数据的形式:使用ajax传输数据,将前台的数据传输到后端
第一种使用form表单中的action形式传输数据:https://blog.csdn.net/weixin_43304253/article/details/120335282 前端页面 <% ...
- 【多服务场景化解决方案】AR虚拟技术助力智能家装
1 .介绍 总览 本应用采用了华为图形引擎服务的AR虚拟技术,您可以在手机相机里摆放想要购置的家具家电,交互式体验让您可以轻松操控它们的3D图例,以此来确定这些家具家电是否适合摆放在您的家里. 特 ...
- 微信小程序——悬浮按钮
关键: position: fixed; wxml: <navigator url="/pages/issue/index"><image class='i ...
- MyBatisPlus 常用知识点总结
@ 目录 完整的Mybatis-Plus项目 常用注解 设置表名(@TableName) 设置实体类字段 (@TableField) 通过 @TableField(fill=FieldFill.INS ...
- 7.Vue常用属性
1. data:数据属性 在之前的学习中我们已经了解到了data,属性中存放的就是js变量 <script> new Vue({ el: '#app', // data data: { u ...
- VBA粗犷整理
PART1: 三.查找 1.从某一行向上/下找到第一个不为空的行 intRowPntEnd = ActiveSheet.Cells(intRowPntStart, intColPnt).End(xlD ...
- SQL server 操作相关
1.更改列的顺序后进行保存. 在SQL Server Management Studio中, "工具"--"选项"--"Designers" ...
- bootstrap-table参数
table.bootstrapTable({ url:'/Home/geurl', //请求后台的URL(*) method:'get', //请求方式(*) toolbar:'#toolbar', ...