zoukankan      html  css  js  c++  java
  • CF482E ELCA

    ELCA

    给一棵(n)个点以(1)为根的树,每个点有权值(s_i),支持两种操作:

    1. (x)及其子树连到(y)上。

    2. (s_x=v)

    每次操作后都询问expected value written on the lowest common ancestor of two equiprobably selected vertices (i) and (j). Please note that the vertices (i) and (j) can be the same (in this case their lowest common ancestor coincides with them).

    (nleq 5 imes 10^4)

    题解

    没人看得懂的题解:https://www.cnblogs.com/suncongbo/p/11330695.html

    表面上是期望问题,实际上是计数问题。统计节点(i)作为LCA的次数即可。

    [E=frac{1}{n^2}sum_{i=1}^ns_ileft(1+2( ext{siz}_i-1)+sum_{p_j=i}sum_{p_k=i,k eq j} ext{siz}_j ext{siz}_k ight) ]

    注意到

    [sum_{p_j=i}sum_{p_k=i,k eq j} ext{siz}_j ext{siz}_k =left(sum_{p_j=i} ext{siz}_j ight)^2-sum_{p_j=i} ext{siz}_j^2 =( ext{siz}_i-1)^2-sum_{p_j=i} ext{siz}_j^2 ]

    所以有

    [E=frac{1}{n^2}sum_{i=1}^ns_ileft( ext{siz}_i^2-sum_{p_j=i} ext{siz}_j^2 ight) ]

    所以对树(T)我们需要维护

    [egin{gather} A_T=sum_{iin T}s_i ext{siz}_i^2\ B_T=sum_{iin T}s_isum_{p_j=i} ext{siz}_j^2\ end{gather} ]

    LCT维护子树信息的时候,Splay里面要按照中序遍历所确定的链来维护。我一稿居然按照Splay的二叉树关系维护了……

    考虑push_up的时候发生的事。带imag_前缀的表示虚子树的信息。

    1. 首先是( ext{siz}_{T(x)})的维护。(T(x))代表(x)在Splay里的子树的中序遍历所确定的实链加上它们的虚子树构成的树。

      [ ext{siz}_{T(x)}= ext{siz}_{T( ext{rc})}+ ext{imag_siz}_x+1+ ext{siz}_{T( ext{lc})} ]

    2. 其次是(A_{T(x)})的维护。(x)自己的贡献和(x)右子树的贡献比较好算,但是(x)在Splay中左子树里的节点(在实链上的)就不一样了,因为它们的子树大小都发生了变化。考虑变化量,(s_i( ext{siz}_i+Delta)^2-s_i ext{siz}_i^2=s_iDelta^2+2s_i ext{siz}_iDelta),所以我们还要维护Splay链上(不包含虚子树)的(s_x)(s_x ext{siz}_x)的和。对实链(L)

      [egin{gather} ext{sum_s}_L=sum_{iin L}s_i\ A'_L=sum_{iin L}s_i ext{siz}_i end{gather} ]

      (Delta= ext{siz}_{T( ext{rc})}+1+ ext{imag_siz}_x),那么有

      [egin{gather} ext{sum_s}_{L(x)}= ext{sum_s}_{L( ext{rc})}+s_x+ ext{sum_s}_{L( ext{lc})}\ A'_{L(x)}=A'_{L( ext{rc})}+s_xDelta+A'_{L( ext{lc})}+ ext{sum_s}_{L( ext{lc})}Delta\ A_{T(x)}=A_{T( ext{rc})}+ ext{imag_A}_x+s_xDelta^2+A_{T( ext{lc})}+2A'_{L( ext{lc})}Delta+ ext{sum_s}_{L( ext{lc})}Delta^2 end{gather} ]

    3. 最后是(B_{T(x)})的维护。同样考虑(x)在Splay中左子树里的节点,它们的变化量要诡异一点,不过跟(A)大同小异。记( ext{son}_i)表示节点(i)在实链剖分中的重儿子,

      [egin{gather} B'_L=sum_{iin L}s_i ext{siz}_{ ext{son}_i} end{gather} ]

      那么有

      [egin{gather} B'_{L(x)}=B'_{L( ext{rc})}+s_x ext{siz}_{T( ext{rc})}+B'_{L( ext{lc})}+ ext{sum_s}_{T( ext{lc})}Delta\ B_{T(x)}=B_{T( ext{rc})}+ ext{imag_B}_x+s_x( ext{imag_sq_siz}_x+ ext{siz}_{T( ext{rc})}^2)+B_{T( ext{lc})}+2B'_{L( ext{lc})}Delta+ ext{sum_s}_{L( ext{lc})}Delta^2 end{gather} ]

    当然了,为了支持reverse操作,我们还需要对(A,B,A',B')维护一个合并方向相反的值。

    维护好这些,易得答案( ext{ans}=frac{1}{n^2}(A_{T( ext{root})}-B_{T( ext{root})}))

    你已经完全会了,快点开始写bug吧!

    时间复杂度(O(nlog n)),但常数估计都抵一个log了。

    #include<bits/stdc++.h>
    using namespace std;
    using int64=long long;
    
    template<class T>
    T read(){
        T x=0;char w=1,c=getchar();
        for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        return x*w;
    }
    template<class T>
    T& read(T& x){
        return x=read<T>();
    }
    
    constexpr int N=5e4+10;
    int ch[N][2],fa[N],rev[N];
    int64 siz[N],imag_siz[N],imag_sq_siz[N];
    int64 s[N],sum_s[N];
    int64 A[N][2],A1[N][2],imag_A[N]; // A1 -> A'
    int64 B[N][2],B1[N][2],imag_B[N]; // B1 -> B'
    
    #define lc ch[x][0]
    #define rc ch[x][1]
    // helper function
    bool nroot(int x){
        return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
    }
    void push_up(int x){
        siz[x]=siz[rc]+imag_siz[x]+1+siz[lc];
        sum_s[x]=sum_s[rc]+s[x]+sum_s[lc];
        // right -> left
        int64 D=siz[rc]+1+imag_siz[x];
        A1[x][0]=A1[rc][0]+s[x]*D+A1[lc][0]+sum_s[lc]*D;
        A[x][0]=A[rc][0]+imag_A[x]+s[x]*D*D+A[lc][0]+2*A1[lc][0]*D+sum_s[lc]*D*D;
        B1[x][0]=B1[rc][0]+s[x]*siz[rc]+B1[lc][0]+sum_s[lc]*D;
        B[x][0]=B[rc][0]+imag_B[x]+s[x]*(imag_sq_siz[x]+siz[rc]*siz[rc])+B[lc][0]+2*B1[lc][0]*D+sum_s[lc]*D*D;
        // left -> right
        D=siz[lc]+1+imag_siz[x];
        A1[x][1]=A1[lc][1]+s[x]*D+A1[rc][1]+sum_s[rc]*D;
        A[x][1]=A[lc][1]+imag_A[x]+s[x]*D*D+A[rc][1]+2*A1[rc][1]*D+sum_s[rc]*D*D;
        B1[x][1]=B1[lc][1]+s[x]*siz[lc]+B1[rc][1]+sum_s[rc]*D;
        B[x][1]=B[lc][1]+imag_B[x]+s[x]*(imag_sq_siz[x]+siz[lc]*siz[lc])+B[rc][1]+2*B1[rc][1]*D+sum_s[rc]*D*D;
    }
    void reverse(int x){
        swap(A1[x][0],A1[x][1]);
        swap(A[x][0],A[x][1]);
        swap(B1[x][0],B1[x][1]);
        swap(B[x][0],B[x][1]);
        swap(ch[x][0],ch[x][1]);
        rev[x]^=1;
    }
    void push_down(int x){
        if(rev[x]){
            if(ch[x][0]) reverse(ch[x][0]);
            if(ch[x][1]) reverse(ch[x][1]);
            rev[x]=0;
        }
    }
    // basic operation
    void rotate(int x){
        int y=fa[x],z=fa[y],l=x==ch[y][1],r=l^1;
        if(nroot(y)) {ch[z][y==ch[z][1]]=x;} fa[x]=z;
        ch[y][l]=ch[x][r],fa[ch[x][r]]=y;
        ch[x][r]=y,fa[y]=x;
        push_up(y);
    }
    void update(int x){
        if(nroot(x)) update(fa[x]);
        push_down(x);
    }
    void splay(int x){
        update(x);
        for(;nroot(x);rotate(x)){
            int y=fa[x],z=fa[y];
            if(nroot(y)) rotate((x==ch[y][1])!=(y==ch[z][1])?x:y);
        }
        push_up(x);
    }
    void access(int x){
        for(int y=0;x;y=x,x=fa[x]){
            splay(x);
            imag_siz[x]+=siz[ch[x][1]]-siz[y];
            imag_sq_siz[x]+=siz[ch[x][1]]*siz[ch[x][1]]-siz[y]*siz[y];
            imag_A[x]+=A[ch[x][1]][0]-A[y][0];
            imag_B[x]+=B[ch[x][1]][0]-B[y][0];
            ch[x][1]=y;
            push_up(x);
        }
    }
    // advanced operation
    void make_root(int x){
        access(x),splay(x),reverse(x);
    }
    int find_root(int x){
        access(x),splay(x);
        while(ch[x][0]) x=ch[x][0];
        splay(x);
        return x;
    }
    void split(int x,int y){
        make_root(x),access(y),splay(y);
    }
    void link(int x,int y){
        make_root(x),access(y),splay(y);
        imag_siz[y]+=siz[x];
        imag_sq_siz[y]+=siz[x]*siz[x];
        imag_A[y]+=A[x][0];
        imag_B[y]+=B[x][0];
        fa[x]=y;
        push_up(y);
    }
    void cut(int x,int y){
        split(x,y);
        fa[x]=ch[y][0]=0;
        push_up(y);
    }
    
    int main(){
        int n=read<int>(),p[N];
        for(int i=2;i<=n;++i) read(p[i]);
        for(int i=1;i<=n;++i) read(s[i]),push_up(i);
        for(int i=2;i<=n;++i) link(i,p[i]);
        make_root(1);
        printf("%.9lf
    ",1.0*(A[1][0]-B[1][0])/n/n);
        for(int q=read<int>();q--;){
            char o[2];
            scanf("%s",o);
            if(o[0]=='P'){
                int x=read<int>(),y=read<int>();
                make_root(1),access(y),splay(y);
                int i=x;
                while(nroot(i)) i=fa[i];
                if(i==y) swap(x,y);
                access(x),splay(x);
                fa[ch[x][0]]=0,ch[x][0]=0;
                push_up(x);
                link(x,y);
            }
            else{
                int x=read<int>();
                make_root(x);
                read(s[x]);
                push_up(x);
            }
            make_root(1);
            printf("%.9lf
    ",1.0*(A[1][0]-B[1][0])/n/n);
        }
        return 0;
    }
    
    静渊以有谋,疏通而知事。
  • 相关阅读:
    如何正确使用 Composer 安装 Laravel 扩展包
    sql之left join、right join、inner join的区别
    mysql decimal类型与decimal长度用法详解
    mysql数据库操作
    linux下如何查看某软件是否已安装
    MySQL数据类型和常用字段属性总结
    mysql数据库字段类型的选择原则
    linux shell 指令 诸如-d, -f, -e之类的判断表达式
    常用的Homebrew命令
    windows下安装php5.5的redis扩展
  • 原文地址:https://www.cnblogs.com/autoint/p/15488868.html
Copyright © 2011-2022 走看看