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) 和 ...
随机推荐
- 没有使用IaC的DevOps系统都是耍流氓
作为现代软件工程的基础实践,基础设施即代码(Infrastructure as Code, IaC)是云原生.容器.微服务以及DevOps背后的底层逻辑.应该说,以上所有这些技术或者实践都是以基础设施 ...
- 基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成
在前面随笔<基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理>中我们已经介绍过,对于相关的业务表的界面代码,我们已经尽可能把不同的业务逻辑 ...
- CURL提交--POST/GET-带header信息
function https_request($url, $param, $data = '', $method = 'GET', $headers = '') { $opts = array( CU ...
- python的微积分运算
import sympy sympy.init_printing() from sympy import I, pi, oo import numpy as np 求函数的导数 x = sympy.S ...
- 27.路由器Routers
像一些reils这样的web框架提供自动生成urls的功能,但是Django没有 rest framework为django添加了这一功能,以一种简单.快速.一致的方式 routers必须配合view ...
- 【MySQL】03_数据类型
MySQL 中的数据类型 类型 类型举例 整数类型 TINYINT.SMALLINT.MEDIUMINT.INT(或INTEGER).BIGINT 浮点类型 FLOAT.DOUBLE 定点数类型 DE ...
- 线上kafka消息堆积,consumer掉线,怎么办?
线上kafka消息堆积,所有consumer全部掉线,到底怎么回事? 最近处理了一次线上故障,具体故障表现就是kafka某个topic消息堆积,这个topic的相关consumer全部掉线. 整体排查 ...
- 解决 net core 3.x 跨域问题
跨域:指的是浏览器不能执行其他网站的脚本.它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制. 以下几种情况是造成跨域的原因: 域名相同,端口不同 域名相同,协议不同(即,一个 ...
- .NET 7.0 重磅发布及资源汇总
2022-11-8 .NET 7.0 作为微软的开源跨平台开发平台正式发布.微软在公告中表示.NET 7为您的应用程序带来了C# 11 / F# 7,.NET MAUI,ASP.NET Core/Bl ...
- Sqlite 安装操作使用
一.什么是 SQLite 数据库 SQLite 是嵌入式SQL数据库引擎.与大多数其他 SQL 数据库不同,SQLite 没有单独的服务器进程.SQLite 直接读取和写入普通磁盘文件.具有多个表,索 ...