AmazingCounters.com

[UOJ]#33. 【UR #2】树上GCD

题目大意:给定一棵有根树,边长均为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\n",ans[i]+ss[i]);
}

 

posted on 2017-09-10 08:42  ditoly  阅读(530)  评论(0编辑  收藏  举报