题目分析:

树上点对问题首先想到点分治。假设我们进行了点分治并递归地解决了子问题。现在我们合并问题。

我们需要找到所有经过当前重心$ c $的子树路径。第一种情况是LCA为当前重心$ c $。考虑以$ 1 $为根的$ c $的子树。那么首先在子问题中先斥掉不经过$ c $的路径。然后对于$ c $的子树处理出距离数组。用桶存储。

从大到小枚举最大公因数$ d $,求出所有距离为$ d $倍数的点的个数。然后做乘法得到$ num1 $。再考虑$ num1 $中GCD不等于$ d $的数有哪些。实际上我们早就计算出了GCD为$ d $的倍数的情况了,只需要把这一部分斥掉就行了。所以处理$ c $的子树的点对的时间复杂度是$ O(nlogn) $。

再考虑$ c $的祖先的儿子到$ c $的子树中的点的情况。不难想到类似的处理方法。开个桶存储距离。由于点分治的特性。我们很容易就可以证明这样所有桶的最大值之和是不会超过$ O(n) $的。这样枚举的时间复杂度是有保障的。对于$ c $的某个祖先的子树中的点,枚举GCD为$ d $。 那么我们要在$ c $的子树中找到所有的距$ c $祖先$ d $的倍数的点。 由于我们拔高了LCA,这时候我们不能简单地枚举倍数。重新审视问题,发现其实它是求的c中从i开始每隔j个的和。采用分块算法,对于小于等于$ \sqrt{n} $的情况共有$ \sqrt{n} $个不同的起点。每个不同的间隔都会完全覆盖依次整个桶。共覆盖了$ \sqrt{n} $次。所以预处理的时间复杂度为$ O(n*\sqrt{n}) $。对于大于$ \sqrt{n} $的情况可以暴力计算。

我们每处理一个重心的时间复杂度为$ O(n*\log n+n*\sqrt{n}) $分为两半,前半部分总时间复杂度为$ O(n*\log n*\log n) $,后半部分带入主定理知为$ O(n*\sqrt{n}) $

代码:

 #include<bits/stdc++.h>
using namespace std; const int maxn = ; int n;
int dep[maxn],f[maxn];
long long ans[maxn];
vector <int> g[maxn]; int arr[maxn],sz[maxn],dg[maxn],presum[maxn]; void read(){
scanf("%d",&n);
for(int i=;i<=n;i++) {
scanf("%d",&f[i]);
g[f[i]].push_back(i);
g[i].push_back(f[i]);
}
} void DFS(int now,int dp) {
dep[now] = dp;
for(int i=;i<g[now].size();i++){
if(g[now][i] == f[now]) continue;
DFS(g[now][i],dp+);
}
} void dfs1(int now,int fa){
sz[now] = ;dg[now] = ;
for(int i=;i<g[now].size();i++){
if(g[now][i] == fa) continue;
if(arr[g[now][i]]) continue;
dfs1(g[now][i],now);
sz[now] += sz[g[now][i]];
dg[now] = max(dg[now],sz[g[now][i]]);
}
sz[now]++;
} int dfs2(int now,int fa,int tot){
int res = now;dg[now] = max(tot-sz[now],dg[now]);
for(int i=;i<g[now].size();i++){
if(g[now][i] == fa) continue;
if(arr[g[now][i]]) continue;
int z = dfs2(g[now][i],now,tot);
if(dg[res] > dg[z])res = z;
}
return res;
} int depest,h[maxn],sum[maxn];
long long nowans[maxn];
int Final[][]; int oth,arv[maxn],s2[maxn]; void dfs3(int now,int len,int stop){
h[len]++;depest = max(depest,len);
for(int i=;i<g[now].size();i++){
if(g[now][i] == f[now]) continue;
if(arr[g[now][i]]!= && arr[g[now][i]]<=stop) continue;
dfs3(g[now][i],len+,stop);
}
} void dfs4(int now,int cant,int stop,int lca){
arv[dep[now]-dep[lca]]++; oth = max(oth,dep[now]-dep[lca]);
for(int i=;i<g[now].size();i++){
if(g[now][i] == f[now] || g[now][i] == cant) continue;
if(arr[g[now][i]]!= && arr[g[now][i]]<=stop) continue;
dfs4(g[now][i],cant,stop,lca);
}
} void solve_dec(int now,int last,int ht){
depest = ;
dfs3(now,,ht);
for(int i=depest;i>=;i--){
for(int j=;i*j<=depest;j++) sum[i] += h[i*j];
nowans[i] = 1ll*sum[i]*(sum[i]-)/;
for(int j=;i*j<=depest;j++) nowans[i]-=nowans[i*j];
ans[i] -= nowans[i];
}
for(int i=;i<=depest;i++) h[i] = ,sum[i] = ,nowans[i] = ;
} void solve_add(int now,int ht){
depest = ;
dfs3(now,,ht);
for(int i=depest;i>=;i--){
for(int j=;i*j<=depest;j++) sum[i] += h[i*j]; //multi d
nowans[i] = 1ll*sum[i]*(sum[i]-)/;
for(int j=;i*j<=depest;j++) nowans[i]-=nowans[i*j];
ans[i] += nowans[i];
}
for(int i=;i<=depest;i++) nowans[i] = ; for(int i=;i*i<=depest;i++) for(int j=;j<i;j++)
for(int k=j;k<=depest;k+=i) Final[i][j] += h[k];
// in subtree int tp = f[now],last = now,rem = ;
while(tp&&(arr[tp]>ht||arr[tp] == )){
oth = ;rem++;
dfs4(tp,last,ht,tp); // tp mean lca
for(int i=oth;i>=;i--){
for(int j=;i*j<=oth;j++) s2[i] += arv[i*j];
if(i*i<=depest)
nowans[i] = 1ll*s2[i]*Final[i][(((-rem)%i)+i)%i];
else{
int frst = (((-rem)%i)+i)%i;
int tot = ;
for(int k =frst;k<=depest;k+=i) tot += h[k];
nowans[i] = 1ll*s2[i]*tot;
}
for(int j=;i*j<=oth;j++) nowans[i] -= nowans[i*j];
ans[i] += nowans[i];
}
last = tp; tp = f[tp];
for(int i=;i<=oth;i++) arv[i] = s2[i] = nowans[i] = ;
}
//out subtree
for(int i=;i<=depest;i++) h[i] = ,sum[i] = ;
for(int i=;i*i<=depest;i++) for(int j=;j<i;j++) Final[i][j] = ;
} void divide(int now,int last,int ht){
dfs1(now,); int pw = sz[now];
int pt = dfs2(now,,pw);
arr[pt] = ht;
for(int i=;i<g[pt].size();i++){
if(arr[g[pt][i]]) continue;
divide(g[pt][i],pt,ht+);
}
if(last&&f[now]==last) solve_dec(now,last,ht-);
solve_add(pt,ht);
} void work(){
DFS(,);
divide(,,);
for(int i=;i<=n;i++) presum[dep[i]-]++;
for(int i=n;i>=;i--) presum[i] += presum[i+];
for(int i=;i<=n;i++) ans[i] += presum[i];
for(int i=;i<n;i++) printf("%lld\n",ans[i]);
} int main(){
read();
work();
return ;
}

UOJ33 [UR #2] 树上GCD 【点分治】【容斥原理】【分块】的更多相关文章

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

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

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

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

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

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

  4. [UOJ UR #2]树上GCD

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

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

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

  6. poj1741 树上的点分治

    题意: 一棵10000个点的树,每条边的长不超过1000,给定一个值k,问距离不超过k的点对数有多少.(多组数据) 输入样例: 5 4 1 2 3 1 3 1 1 4 2 3 5 1 0 0输出样例: ...

  7. 【UOJ#50】【UR #3】链式反应(分治FFT,动态规划)

    [UOJ#50][UR #3]链式反应(分治FFT,动态规划) 题面 UOJ 题解 首先把题目意思捋一捋,大概就是有\(n\)个节点的一棵树,父亲的编号大于儿子. 满足一个点的儿子有\(2+c\)个, ...

  8. [BZOJ 2820] YY的gcd(莫比乌斯反演+数论分块)

    [BZOJ 2820] YY的gcd(莫比乌斯反演+数论分块) 题面 给定N, M,求\(1\leq x\leq N, 1\leq y\leq M\)且gcd(x, y)为质数的(x, y)有多少对. ...

  9. 还不会做! 树上的gcd 树分治 UOJ33

    题目链接:http://uoj.ac/problem/33 题解链接:http://vfleaking.blog.uoj.ac/blog/38 现在感觉到了做OI的层层递进的思路的伟大之处,作为一个大 ...

随机推荐

  1. 每个大主播都是满屏弹幕,怎么做到的?Python实战无限刷弹幕!

    anmu 是一个开源的直播平台弹幕接口,使用他没什么基础的你也可以轻松的操作各平台弹幕.使用不到三十行代码,你就可以使用Python基于弹幕进一步开发.支持斗鱼.熊猫.战旗.全民.Bilibili多平 ...

  2. git使用备注

    git clone 代码库地址 git branch -r  查看远程分支 git branch 查看本地分支 git branch -a 查看远程和本地分支.带*的表示正在所处分支. git bra ...

  3. java算法----排序----(2)选择排序

    package log; public class Test4 { /** * java算法---选择排序 * * @param args */ public static void main(Str ...

  4. linux系统原子操作

    一.概念 原子操作提供了指令原子执行,中间没有中断.就像原子被认为是不可分割颗粒一样,原子操作(atomic operation)是不可分割的操作.      c语言中一个变量的自加1操作,看起来很简 ...

  5. Luogu2045 方格取数加强版(K取方格数) 费用流

    题目传送门 题意:给出一个$N \times N$的方格,每个格子中有一个数字.你可以取$K$次数,每次取数从左上角的方格开始,每一次只能向右或向下走一格,走到右下角结束,沿路的方格中的数字将会被取出 ...

  6. 腾讯云 ubuntu 上tomcat加载项目很慢

    问题原因 随机数引起线程阻塞. tomcat不断启动,关闭, 启动关闭.几次后会出现卡死状况.需很久才能加载完成 阿里云同样配置,同样系统,则很难出现卡死状况.  即使出现过几十秒后也会释放出来. 而 ...

  7. Python 学习 第八篇:函数2(参数、lamdba和函数属性)

    函数的参数是参数暴露给外部的接口,向函数传递参数,可以控制函数的流程,函数可以0个.1个或多个参数:在Python中向函数传参,使用的是赋值方式. 一,传递参数 参数是通过赋值来传递的,传递参数的特点 ...

  8. Telephone Phrases

    There are some common phrases and sentences you can use when speaking on the telephone. The informal ...

  9. PHP 设置调试工具XDebug PHPStorm IDE

    先下载PHP扩展Xdebug https://xdebug.org, 可以复制自己的phpinfo粘贴到https://xdebug.org/wizard.php中, 会生成需要下载的版本, php. ...

  10. Natural Language Generation/Abstractive Summarization

    调研目的: 了解生成式文本摘要的常用技术和当前的发展趋势,明确当前项目有什么样的摘要需求,判断现有技术能否用于满足当前的需求,进一步明确毕业设计方向及其可行性 调研方向: 项目中需要用到摘要的地方以及 ...