CF482E ELCA 和 LOJ558 我们的 CPU 遭到攻击

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).

\(n\leq 5\times 10^4\)

题解

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

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

\[E=\frac{1}{n^2}\sum_{i=1}^ns_i\left(1+2(\text{siz}_i-1)+\sum_{p_j=i}\sum_{p_k=i,k\neq j}\text{siz}_j\text{siz}_k\right) \]

注意到

\[\sum_{p_j=i}\sum_{p_k=i,k\neq j}\text{siz}_j\text{siz}_k =\left(\sum_{p_j=i}\text{siz}_j\right)^2-\sum_{p_j=i}\text{siz}_j^2 =(\text{siz}_i-1)^2-\sum_{p_j=i}\text{siz}_j^2 \]

所以有

\[E=\frac{1}{n^2}\sum_{i=1}^ns_i\left(\text{siz}_i^2-\sum_{p_j=i}\text{siz}_j^2\right) \]

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

\[\begin{gather} A_T=\sum_{i\in T}s_i\text{siz}_i^2\\ B_T=\sum_{i\in T}s_i\sum_{p_j=i}\text{siz}_j^2\\ \end{gather} \]

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

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

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

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

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

    \[\begin{gather} \text{sum_s}_L=\sum_{i\in L}s_i\\ A'_L=\sum_{i\in L}s_i\text{siz}_i \end{gather} \]

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

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

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

    \[\begin{gather} B'_L=\sum_{i\in L}s_i\text{siz}_{\text{son}_i} \end{gather} \]

    那么有

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

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

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

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

时间复杂度\(O(n\log 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\n",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\n",1.0*(A[1][0]-B[1][0])/n/n);
    }
    return 0;
}

我们的 CPU 遭到攻击

给你一个有 \(n\) 个点的森林,点有黑白两种颜色,初始时所有点都是白色,森林的每条边有边权,初始时这个森林有 \(m\) 条边。

对这个森林进行 \(k\) 次操作,操作有三种:

  • L u v w:添加一条连接 \(u\)\(v\),长度为 \(w\) 的边。

  • C u v:删除连接 \(u\)\(v\) 的边(保证存在)。

  • F u:反转点 \(u\) 的颜色(黑变白,白变黑)。

  • Q u:询问所有与 \(u\) 相连的黑点到 \(u\) 的距离之和。(相连指的是在同一连通块中

\(0\le k< n\le 10^5\)\(m\le 3\times 10^5\)\(|w|\le 10^7\),保证任何时刻这个图均为森林(即不会出现环)。

题解

https://jklover.hs-blog.cf/2019/12/27/LCT-题目选做/

LCT 维护子树信息.

把边拆成点,变成维护路径点权之和.

由于 LCT 支持换根,所以只需要分别维护出一棵 Splay 中所有点到深度最小,最大的点的距离和,翻转的时候交换.

为了能合并 Splay 左右儿子的信息,还需要维护出子树内黑点的数目,虚边的子树内黑点到对应子树根的距离和.

当虚边被更改时将对应信息更新就可以了,时间复杂度 \(O(n\log n)\).

CO int N=5e5+10;
int ch[N][2],fa[N],rev[N];
int col[N],si[N],siz[N]; // size of imaginary subtree
int val[N];int64 sumv[N]; // sum of weight of edge
int64 sumi[N],suml[N],sumr[N]; // sum of distance of imaginary subtree

IN bool nroot(int x){
	return ch[fa[x]][0]==x or ch[fa[x]][1]==x;
}
IN void push_up(int x){
	siz[x]=col[x]+si[x]+siz[ch[x][0]]+siz[ch[x][1]];
	sumv[x]=val[x]+sumv[ch[x][0]]+sumv[ch[x][1]];
	suml[x]=sumi[x]+suml[ch[x][0]]+suml[ch[x][1]]+(col[x]+si[x]+siz[ch[x][1]])*(val[x]+sumv[ch[x][0]]);
	sumr[x]=sumi[x]+sumr[ch[x][0]]+sumr[ch[x][1]]+(col[x]+si[x]+siz[ch[x][0]])*(val[x]+sumv[ch[x][1]]);
}
IN void reverse(int x){
	swap(ch[x][0],ch[x][1]);
	swap(suml[x],sumr[x]);
	rev[x]^=1;
}
IN 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;
	}
}
IN 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 splay(int x){
	vector<int> stk(1,x);
	for(int i=x;nroot(i);) stk.push_back(i=fa[i]);
	for(;stk.size();stk.pop_back()) push_down(stk.back());
	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);
		si[x]+=siz[ch[x][1]];
		sumi[x]+=suml[ch[x][1]];
		ch[x][1]=y;
		si[x]-=siz[ch[x][1]];
		sumi[x]-=suml[ch[x][1]];
		push_up(x);
	}
}
IN void make_root(int x){
	access(x),splay(x),reverse(x);
}
int find_root(int x){
	access(x),splay(x);
	for(;ch[x][0];x=ch[x][0]);
	splay(x);
	return x;
}
IN void split(int x,int y){
	make_root(x),access(y),splay(y);
}
IN void link(int x,int y){
	make_root(x),access(y),splay(y);
	fa[x]=y;
	si[y]+=siz[x];
	sumi[y]+=suml[x];
	push_up(y);
}
IN void cut(int x,int y){
	split(x,y);
	fa[x]=0;
	ch[y][0]=0;
	push_up(y);
}

int main(){
	int n=read<int>(),m=read<int>(),q=read<int>();
	for(int i=1;i<=m;++i){
		int x=read<int>(),y=read<int>(),w=read<int>();
		val[++n]=w;
		push_up(n);
		link(x,n),link(y,n);
	}
	for(int i=1;i<=q;++i){
		char opt[2];scanf("%s",opt);
		if(opt[0]=='L'){
			int x=read<int>(),y=read<int>(),w=read<int>();
			val[++n]=w;
			push_up(n);
			link(x,n),link(y,n);
		}
		else if(opt[0]=='C'){
			int x=read<int>(),y=read<int>();
			split(x,y);
			int p=ch[y][0];
			cut(p,x),cut(p,y);
		}
		else if(opt[0]=='F'){
			int x=read<int>();
			access(x),splay(x);
			col[x]^=1;
			push_up(x);
		}
		else{
			int x=read<int>();
			make_root(x);
			printf("%lld\n",suml[x]);
		}
	}
	return 0;
}

posted on 2021-10-31 11:40  autoint  阅读(157)  评论(0编辑  收藏  举报

导航