• [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
    ",ans[i]+ss[i]);
    }
  • 相关阅读:
    Linux下C程序插入执行shell脚本
    #ifdef预编译相关用法
    LAMP开发之环境搭建(2014.12.7在ubuntu下)
    Qt在VS2010的安装与配置
    vs2010配备boost编程环境
    Ubuntu虚拟机与Window、Arm的通信
    大小端测试程序
    Ubuntu安装google Gtest
    设计模式之单例模式
    设计模式之原型模式
  • 原文地址:https://www.cnblogs.com/ditoly/p/UOJ33.html
Copyright © 2020-2023  润新知