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


    题目链接:http://uoj.ac/problem/33

    题解链接:http://vfleaking.blog.uoj.ac/blog/38

    现在感觉到了做OI的层层递进的思路的伟大之处,作为一个大学才开始接触C的人只能orz了

    算法一:

    傻逼暴力+lca,所以树O(n*n*logn)

    所以10分

    算法二:(orz我竟然看了半天)

    对于随机生成的树,那么树的高度都是log层的,所以省略去算法一中的傻逼暴力。因为每一层的树高都是log,所以我们只需要暴力树根u,然后以u为根,dfs(u)的所有子节点,并且用cnt[h]记录高度为h的子树的个数即可。最后我们就是暴力h1和h2,然后用cnt[h1]*cnt[h2],然后加入到ans[gcd(h1, h2)]里面即可。因为h1和h2的高度是log,所以我们最终复杂度为O(N * logn * logn)。并且dfs也都是dfs子树,子树的平均分摊也肯定是logn次

    所以30分

    算法三:(早上看的时候完全没有反应过来,上完一下午的课回来再看发现,还是很难= =)

    通过算法二的提示我们发现,我们只需要知道ans[gcd(h1, h2)]而已,所以,我们假定d=gcd(h1, h2),那么对于每一条链,设他的高度为hi,那么他为d倍数的个数为hi/d个,

    策爷的代码,怪我水平不够,代码看不懂

    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<iostream>
    #include<cstdio>
    //#include<ctime>
    #define MAX 200005
    using namespace std;
    int n;
    struct edge{int v,next;}e[MAX];int etot=0;int g[MAX];
    void addedge(int u,int v){///建立有根树
        e[etot].v=v;e[etot].next=g[u];g[u]=etot++;
    }
    typedef long long ll;
    ll ans[MAX]={0};///ans(i)表示两棵子树间深度为i的倍数的值的个数
    ll ans2[MAX]={0};
    int h[MAX],pre[MAX];///h表示目前树的高度,pre(x)表示节点x的fa
    int off[MAX]={0};///off(i)表示目前是否对i节点打标记,就表示是否成为过重心
    
    /**orz 这重心找的太强了,比dfs不知道快多少*/
    int qu[MAX],sz[MAX],bobo[MAX];//这个bobo应该是bool的意思,表示是否能选择
    int findc(int rt){
        int p=0,q=0;
        qu[q++]=rt;///就是queue
        while(p!=q){///bfs对所有子节点进行初始化(当然,也可以在这里进行pre节点的修改)
            int u=qu[p++];
            bobo[u]=sz[u]=1;
            for (int i=g[u];~i;i=e[i].next)if(!off[e[i].v])///如果没有访问过,就刚入bfs的节点
                qu[q++]=e[i].v;
        }
        for (int i=q-1;i>=0;i--){///迭代使用
            if(bobo[qu[i]] && sz[qu[i]]*2>=q)return qu[i];///重心肯定是树size的一半,所以sz肯定也是重心的一半左右
            sz[pre[qu[i]]]+=sz[qu[i]];///对他的父亲节点的sz进行更新
            if(sz[qu[i]]*2>q)bobo[pre[qu[i]]]=0;///如果目前节点的sz*2比q大了,那么父亲节点也肯定比他大了,所以对他的父亲节点都是false
        }
    }
    
    int nub[MAX];///统计这一棵子树,深度为h的有几个
    /**统计深度为h的有几个,并且返回最大深度*/
    int bfs(int rt,int *nub,int hh){///注意,这里的hh可能会发生改变的
        int p=0,q=0;qu[q++]=rt;
        while(p!=q){///bfs出所有的节点
            int u=qu[p++];
            for (int i=g[u];~i;i=e[i].next)if(!off[e[i].v])qu[q++]=e[i].v;
        }
        ///h在main函数的输入中就已经得到了
        int hmax=h[qu[q-1]]-h[rt]+hh;///得到最大深度,因为bfs到的最大深度就是
        for (int i=0;i<=hmax;i++)nub[i]=0;///对目前深度一下的进行初始化
        for (int i=0;i<q;i++){
            nub[h[qu[i]]-h[rt]+hh]++;///统计目前深度的cnt的个数
        }
        /*if (hh == 0){
            for (int i = 0; i < 10; i++){
                printf("%d ", nub[i]);
            }
            cout << endl;
        }
        if (hh == 0) printf("hmax = %d
    ", hmax);*/
        return hmax;
    }
    
    int tmp[MAX];///tmp[j]就是保存所有子树中被j整除的个数
    ll tmpsq[MAX];///tmpsq[j]是保存了所有子树中自身两两搭配的个数
    int nsu[MAX];///nsu表示所有子树的高度为j的数目
    int tmpnu[MAX],*start[MAX],tmph[MAX];
    int ord[MAX];
    int cmp(int i,int j){return tmph[i]<tmph[j];}
    int ind=0;
    int check[MAX]={0},val[MAX];
    
    void work(int rt){
        int c=findc(rt);///找到重心
        off[c]=1;///对重心进行标记
        /*u和v是在同一棵子树里面*/
        int hmax=0;
        ///暴力所有的重心的子节点
        //printf("cetroid = %d
    ", c);
        //这里从来不计算高度=0的时候的东西
        for (int i=g[c];~i;i=e[i].next)if(!off[e[i].v]){
            int h=bfs(e[i].v,nub,1);///这里之所以是1,因为他是重心的子节点
            if(h>hmax){
                ///表示高度为j的是1,并且在这里初始化
                for (int j=hmax+1;j<=h;j++)
                    nsu[j]=0,tmp[j]=tmpsq[j]=1;
                hmax=h;
            }
            for (int j=1;j<=h;j++){
                nsu[j]+=nub[j];///nsu表示所有子树的高度为j的数目
                int sum=0;
                ///k是j的倍数,sum保存能被j整除的数的个数
                for (int k=j;k<=h;k+=j) sum+=nub[k];
                ///tmp[j]就是保存所有子树中被j整除的个数
                ///tmpsq[j]是保存了所有子树中自身两两搭配的个数
                tmp[j]+=sum; tmpsq[j]+=1ll*sum*sum;
            }
        }
        /*但是存在一个疑问点就是,如果两个都是2*d怎么解决呢?*/
        ///tmp[i]*tmp[i]表示所有子树的两两搭配,但是不包括自身的两两搭配
        for (int i=1;i<=hmax;i++)
            ans[i]+=(1ll*tmp[i]*tmp[i]-tmpsq[i])>>1;
    
        int u,num=0;///num表示往上爬几次
        int *st=tmpnu;///st为tmpnu数组
        ///往重心上面的结点爬
    
        for (u=pre[c]; u && !off[u]; u=pre[u]){
            off[u]=1;///对父亲结点打标记
            start[++num]=st;///让start[++num]等于st数组,并且复制给他
            ord[num]=num;///记录编号
            tmph[num]=bfs(u,st,0);///这里之所以为0因为st是根
            ///因为u是之前的fa,所以这里要+1,表示的就是往右边移动一步
    
    //表明st的移动并不对start存在影响
    /*
            printf("num = %d
    ", num);
            cout << "st1" << endl;
             for (int j = 0; j < 10; j++){
                printf("%d ", st[j]);
            }
            cout << endl;
    
            printf("start1
    ");
            for (int i = 0; i < 10; i++)
                printf("%d ", start[num][i]);
            cout << endl;
    */
            st+=tmph[num]+1;///然后让st指针移动maxn deep+1步,把所有的东西给清除掉?
            //for (int j = 0; j <= tmph[num] + 1; j++) st[j] = 0;
    /*
            printf("tmph[%d] = %d
    ", num, tmph[num]);
            cout << "st2" << endl;
            for (int j = 0; j < 10; j++){
                printf("%d ", st[j]);
            }
            cout << endl;
    
            cout << "start2" << endl;
            for (int i = 0; i < 10; i++)
                printf("%d ", start[num][i]);
            cout << endl;
    */
        }
        ///对所有的东西标记去除,因为这里表示的就是不断地往上面走
        for (int v=pre[c]; v!=u; v=pre[v]) off[v]=0;
        /*
        后面这里完全看不懂啊TAT
        */
        for (int i=0;i<=hmax;i++){
            ans2[i+1]+=nsu[i];
            ans2[i+num+1]-=nsu[i];
        }
        ///根据树的高度进行排序
        sort(ord+1,ord+1+num,cmp);
        int tms=0;
        for (int h=1,cur=1;cur<=num;h++){
            ind++;
            ///找到高度>=h的,因为ord是排序好了的
            while(cur<=num && tmph[ord[cur]]<h)cur++;
            for (int i=cur;i<=num;i++){
                int s=0,t;
                ///加上高度是j的倍数的东西
                for (int j=h;j<=tmph[ord[i]];j+=h)s+=start[ord[i]][j];
                if(check[ord[i]%h]==ind)t=val[ord[i]%h],tms++;
                else{
                    int ss=h-(ord[i]-1)%h-1;t=0;
                    for (int j=ss;j<=hmax;j+=h)t+=nsu[j],tms++;
                    check[ord[i]%h]=ind;
                    val[ord[i]%h]=t;
                }
                ans[h]+=1ll*s*t;
            }
        }
        ///也就是说,重心必须是目前的rt
        if(c!=rt)work(rt);
        ///继续dfs
        for (int i=g[c];~i;i=e[i].next)if(!off[e[i].v])work(e[i].v);
    }
    
    int main(){
        nsu[0]=1;///自己到自己的树高就是0
        memset(g,-1,sizeof(g));
        scanf("%d",&n);
        for (int i=2;i<=n;i++){///输入
            int x;scanf("%d",&x);
            h[i]=h[x]+1;pre[i]=x;///因为x是h[i]的fa,所以h[i]的深度比h[x]要大1.然后在此时记录i的父亲是x
            addedge(x,i);///建立单向边,即有根树
            if(x>=i)return 0;///因为题目保证了i>x,所以这个for循环里面可以这么写
        }
        work(1);
        for (int i=n-1;i>=1;i--){
            for (int j=2*i;j<=n-1;j+=i) ans[i]-=ans[j];
        }
        for (int i=2;i<=n-1;i++) ans2[i]+=ans2[i-1];
        for (int i=1;i<=n-1;i++) printf("%lld
    ",ans[i]+ans2[i]);
        return 0;
    }
    /*
    7
    1
    2
    3
    3
    1
    2
    */
    View Code

    然后就是这位大牛的代码,终于看懂了,是我太菜了,晚上我再重敲一遍

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    using namespace std;
    
    int read()
    {
        int x=0,f=1; char ch=getchar();
        while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();}
        while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();}
        return x*f;
    }
    #define maxn 200010
    
    int n,m,fa[maxn],root,block,siz;
    struct EdgeNode{int next,to;}edge[maxn<<1];
    int head[maxn],cnt;
    ///插入双向边
    void add(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;}
    void insert(int u,int v) {add(u,v); add(v,u);}
    /**/
    int size[maxn],maxx[maxn]; bool visit[maxn];
    
    ///得到重心,root就是重心
    void Getroot(int now,int fa){
        size[now]=1; maxx[now]=0;
        for (int i=head[now]; i; i=edge[i].next)
            if (edge[i].to!=fa && !visit[edge[i].to])
                {
                    Getroot(edge[i].to,now);
                    size[now]+=size[edge[i].to];
                    maxx[now]=max(maxx[now],size[edge[i].to]);
                }
        maxx[now]=max(maxx[now],siz-size[now]);
        if (maxx[now]<maxx[root]) root=now;
    }
    
    int num[maxn],deep[maxn];///deep表示目前节点的深度,num[i]表示深度为i的节点有几个
    int tmp;///表示最大深度
    ///得到最大深度
    void DFSdeep(int now,int fa){
        num[deep[now]]++;
        for (int i=head[now]; i; i=edge[i].next)
            if (edge[i].to!=fa && !visit[edge[i].to]){
                deep[edge[i].to]=deep[now]+1;
                DFSdeep(edge[i].to,now);
            }
        if (deep[now]>tmp) tmp=deep[now];
    }
    
    long long S[maxn],Ans[maxn],ans[maxn];
    long long tim[maxn],Num[maxn],Tim[maxn],f[510][510];
    /*
    tim(i)表示当前子树里面的距离为i的倍数的数目
    Tim(i)表示除了当前子树之外的其他子树的距离是i的倍数的数目
    S(i)=tim[i]*Tim[i]表示两个子树之间相乘,就是ans(i)
    num(i)就记录当前子树内距离根为i的有几个点
    Num(i)就表示除了当前子树外,距离根为i的有几个点
    
    f(i,j)表示目前深度为tmp,走j的倍数步的节点的个数是多少?
    
    然后Ans[i]只记录(u, v)在同一条链上的,即LCA(u,v) = u 或者 LCA(u, v) = v
    */
    void GetAns(int now)
    {
        visit[now]=1;
        int maxd=0,Maxd=0;///maxd表示当前子树的最大的deep,Maxd表示其他子树目前最大的Maxdeep
        for (int i=head[now]; i; i=edge[i].next)///暴力子节点,且不暴力fa
            if (!visit[edge[i].to] && edge[i].to!=fa[now])
                {
                    deep[edge[i].to]=1;///对刚开始的位置定义深度为1
                    tmp=0;///tmp表示最深的深度
                    DFSdeep(edge[i].to,now);///寻找最深的深度
                    if (tmp>maxd) maxd=tmp;///修改最大深度
                    for (int j=1; j<=tmp; j++)
                        for (int k=j; k<=tmp; k+=j)
                            tim[j]+=num[k];
                    for (int j=1; j<=tmp; j++)///Ans[j]在这个时候加入,因为高度为j的一定是自己的gcd
                        Num[j]+=num[j],Ans[j]+=num[j],S[j]+=tim[j]*Tim[j],
                        Tim[j]+=tim[j],tim[j]=0,num[j]=0;///对tim和num初始化
                }
        Num[0]=1;///高度为0的在这个时候放入
        int zz=0,ss=now;
        for (int i=fa[now]; !visit[i]&&i; ss=i,i=fa[i])
            {
                tmp=0;  zz++;///zz表示往上面走几步
                for (int j=head[i]; j; j=edge[j].next)///得到目前的最大深度
                    if (edge[j].to!=fa[i] && !visit[edge[j].to] && edge[j].to!=ss)
                        deep[edge[j].to]=1,DFSdeep(edge[j].to,i);
                if (tmp>Maxd) Maxd=tmp;
                for (int j=1; j<=tmp; j++)///计算为k的倍数的东西
                    for (int k=j; k<=tmp; k+=j)
                        tim[j]+=num[k];
                ///tt就是小的那个,tmp表示最大深度,block表示块的大小
                int tt = min(tmp, block);
                for (int j=1; j<=tt; j++)
                    {
                        if (f[j][zz%j]==-1)///小于sqrt(n)的我们提前储存起来分治,这样就防止复杂度过高
                            {
                                f[j][zz%j]=0;
                                for (int k=(j-zz%j)%j; k<=maxd; k+=j){///请注意(j-zz%j)和(j-zz%j)%j之间的区别
                                    f[j][zz%j]+=Num[k];
                                }
                            }
                        ///zz%j其实是一样的
                        S[j]+=f[j][zz%j]*tim[j];
                    }
                for (int j=block+1; j<=tmp; j++)///如果>j的话tim[j]
                    for (int k=(j-zz%j)%j; k<=maxd; k+=j)
                        S[j]+=Num[k]*tim[j];
                ///不放入Num和Tim之中
                for (int j=1; j<=tmp; j++) tim[j]=0,num[j]=0;
                /*这个Ans是做啥的?*/
                Ans[zz]++;///因为每次往上面走一步,所以要加上从重心开始到这个点的cnt
            }
        int l=1,r=0;///这个的作用是,莫队?,表示区间[i-zz, i-1]
        /*这一步也看不懂*/
        long long tmpans=0;
    /*
    5
    1
    2
    3
    4
    */
    /*
        printf("cetroid = %d
    ", now);
        for (int i = 0; i <= maxd + zz; i++)
            printf("%d ", Num[i]);
        cout << endl;
    
        printf("zz = %d maxd = %d
    ", zz, maxd);
    */
    /**
    终于弄明白了这里是什么意思了,Ans(i)的定义是不变的,所以i就表示距离是i的有几个,然后之前不是有一个向上
    爬的过程嘛,另LCA为这个向上爬的每一个点,那么要满足在zz个之中深度在i的只有区间[i-zz,i-1],因为还有一个
    节点并没有算上去!
    */
        for (int i=2; i<=zz+maxd; i++){///往上走了zz步,然后又+上原来的最大深度,所以i表示最大深度
            tmpans += r+1<i ? Num[++r]:0;
            tmpans -= l+zz<i ? Num[l++]:0;
            Ans[i]+=tmpans;
            printf("i = %d l = %d r = %d Ans[%d] = %I64d tmpans = %I64d
    ", i, l, r, i, Ans[i], tmpans);
        }
        int tt = min(Maxd, block);
        ///清空
        for (int i=1; i<=tt; i++)
             for (int j=0; j<=i-1; j++)
                 f[i][j]=-1;
        for (int i=0; i<=maxd; i++) Num[i]=0,Tim[i]=0;
        ///这里是重新开始分治
        for (int i=head[now]; i; i=edge[i].next)
            if (!visit[edge[i].to]){
                root=0;
                siz=size[edge[i].to];
                Getroot(edge[i].to,now);
                GetAns(root);
            }
    }
    
    int main(){
        n=read(); block=(int)sqrt(n);
        for (int i=2; i<=n; i++)
            fa[i]=read(),insert(fa[i],i);
        maxx[root]=0x7fffffff;
        siz=n;
        memset(f,-1,sizeof(f));
        Getroot(1,0);///得到重心
        GetAns(root);///dfs从重心开始
        for (int i=n-1; i; i--){
            ans[i]=S[i];
                for (int j=i+i; j<=n-1; j+=i)///为因为S(i)表示能被i整除的,但是可能存在两个都是大于i的东西的,所以我们这里要容斥一下
                    ans[i]-=ans[j];
        }
        for (int i=1; i<=n-1; i++) printf("%lld
    ",ans[i]+Ans[i]);
        return 0;
    }
    View Code
  • 相关阅读:
    《学习之道》第二章学习方法7看视频
    《学习之道》第二章学习6阅读书籍
    反射详解一
    spring 初始化和销毁的三种方法
    文件读取
    JdbcTemplate 详解二
    JdbcTemplate 详解一
    JdbcTemplate 详解三
    常用commons 工具类依赖配置
    java 8 stream
  • 原文地址:https://www.cnblogs.com/heimao5027/p/6650191.html
Copyright © 2020-2023  润新知