题目大意:给定一棵有根树,边长均为1,对于每一个i,求树上有多少个点对,他们到lca距离的gcd是i。(n<=200,000)
做法:先容斥,求出gcd是i的倍数的点对,考虑长链剖分后从小到大合并计算答案,小的部分先把每个深度的数量变为每个深度的倍数的数量,然后若深度>k,直接到大的里面暴力,若深度<=k,我们在大的里面维护f[i][j]表示深度mod i(1<=i<=k)为j的点数,理论上k取n^0.5时达到最小复杂度O(n^1.5),实际上k比较小的时候常数较小。另外递归计算的时候先递归轻儿子,这样始终都只要存一个f数组。
代码:
#include<cstdio> inline int read() { int x;char c; while((c=getchar())<'0'||c>'9'); for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=x*10+c-'0'; return x; } #define MN 200000 #define K 20 struct edge{int nx,t;}e[MN+5]; int h[MN+5],en,d[MN+5],ht[MN+5],mx[MN+5],l[MN+5],cnt; int f[MN+5],c[MN+5],s[K+5][K+5]; long long ans[MN+5],ss[MN+5]; inline void ins(int x,int y){e[++en]=(edge){h[x],y};h[x]=en;} void dfs(int x) { l[x]=++cnt; if(mx[x])dfs(mx[x]); for(int i=h[x];i;i=e[i].nx)if(e[i].t!=mx[x])dfs(e[i].t); } void solve(int x,int v) { for(int i=h[x];i;i=e[i].nx)if(e[i].t!=mx[x])solve(e[i].t,1); if(mx[x])solve(mx[x],0); for(int i=h[x];i;i=e[i].nx)if(e[i].t!=mx[x]) { for(int j=0;j<=ht[e[i].t];++j) { c[j]=f[l[e[i].t]+j]; for(int k=j;(k+=j+1)<=ht[e[i].t];)f[l[e[i].t]+j]+=f[l[e[i].t]+k]; if(j<K)ans[j+1]+=1LL*f[l[e[i].t]+j]*s[j+1][d[x]%(j+1)]; else for(int k=0;(k+=j+1)<=ht[x];)ans[j+1]+=1LL*f[l[e[i].t]+j]*f[l[x]+k]; } for(int j=0;j<=ht[e[i].t];++j) { f[l[x]+j+1]+=c[j]; for(int k=1;k<=K;++k)s[k][(d[e[i].t]+j)%k]+=c[j]; } } f[l[x]]=1; if(v)for(int i=1;i<=ht[x];++i)for(int k=1;k<=K;++k)s[k][(d[x]+i)%k]-=f[l[x]+i]; else for(int k=1;k<=K;++k)++s[k][d[x]%k]; } int main() { int n=read(),i,j; for(i=2;i<=n;++i)++ss[d[i]=d[j=read()]+1],ins(j,i); for(i=n;i;--i)for(j=h[i];j;j=e[j].nx) if(ht[e[j].t]+1>ht[i])ht[i]=ht[mx[i]=e[j].t]+1; dfs(1);solve(1,0); for(i=n;i;--i)for(ss[j=i]+=ss[i+1];(j+=i)<=n;)ans[i]-=ans[j]; for(i=1;i<n;++i)printf("%lld ",ans[i]+ss[i]); }