UOJ33 【UR #2】树上GCD
感觉像是长剖?具体的,我们思考如何合并两个子树。
因为本质上 gcd 是属于高维前缀和的范畴的,我们使用 \(\text{Dirichlet}\) 后缀和来合并,然后再后缀差分一下。这样我们合并两个子树的复杂度就是 \(O((n+m)\log\log(n+m)\) 的,其中 \(n,m\) 是两棵子树的深度。
但是长剖需要我们快速在链头添加一个数,好像我们使用上面的做法不能轻易加一个值。
考虑了一波,我们对于轻链使用 \(\text{Dirichlet}\) 后缀和来维护,同时枚举其中每一个位置 \(k\) ,那么此时考虑暴力一点的做法就是在重儿子上枚举 \(k\) 的倍数,并查询即可。
考虑这里可以使用根号分治,如果 \(k>\sqrt{n}\) ,那么直接暴力做。如果 \(k\le \sqrt{n}\) ,我们此时可以预处理出重链上的点深度在模 \(k\) 的情况下各个余数的个数,这样我们就可以 \(O(1)\) 查询,同时加入是 \(O(\sqrt{n})\) 的。
感觉这道题目还需要一些 DSU on tree 的技巧,具体来说,就是由于我们那个桶同时只能存入一条重链的信息,所以我们要先处理轻链,然后将轻链从桶中删去,再处理重链(从重儿子处继承),最后再将轻儿子的东西合并上来。
#include
using namespace std;
const int N=2e5+5,B=256;
int n;long long res[N],RES[N];
vector pri;bool tag[N];
void init(int n){
for(int i=2;i<=n;++i){
if(!tag[i]) pri.push_back(i);
for(int j=0;j<(int)pri.size()&&i*pri[j]<=n;++j){
tag[i*pri[j]]=true;if(i%pri[j]==0) break;
}
}
}
struct Edge{int nxt,to;}e[N];int fir[N];
void add(int u,int v,int i){e[i]=(Edge){fir[u],v},fir[u]=i;}
struct Node{int fa,dep,dis,son;}tr[N];
void dfs(int u){
tr[u].dep=tr[u].dis=tr[tr[u].fa].dep+1;
for(int i=fir[u];i;i=e[i].nxt){
int v=e[i].to;if(v==tr[u].fa) continue;
tr[v].fa=u,dfs(v);
if(tr[v].dis>tr[tr[u].son].dis)
tr[u].son=v,tr[u].dis=tr[v].dis;
}
// printf("%d %d %d %d\n",u,tr[u].fa,tr[u].dep,tr[u].dis);
}
struct Bag{
int siz;long long f[B][B],g[N];
void add(int x,long long y){g[x]+=y;for(int i=1;i h[N];
void Dirichlet(int u){
int n=h[u].size();
for(int i=0;i<(int)pri.size()&&pri[i]=1;--j) h[u][j]+=h[u][j*pri[i]];
}
}
void IDirichlet(int u){
int n=h[u].size();
for(int i=0;i<(int)pri.size()&&pri[i]>n,init(n),bag.siz=B;
for(int i=2,fa;i<=n;++i) scanf("%d",&fa),add(fa,i,i);
tr[0].dep=-1,dfs(1),DFS(1);
for(int i=tr[1].dep;i<=tr[1].dis;++i)
h[1].push_back(bag.g[i]),bag.clear(i);
for(int i=1;i<(int)h[1].size();++i)
RES[1]+=h[1][i],RES[i+1]-=h[1][i];
for(int i=0;i<(int)pri.size()&&pri[i]<=tr[1].dis;++i){
for(int j=1;j*pri[i]