• P4219 [BJOI2014]大融合


    https://www.luogu.com.cn/problem/P4219
    https://darkbzoj.tk/submission/80566

    (n) 个点,起初没有边,每次加一个边或查询对于一条边,有多少条不同的路径经过它,保证时刻是一个森林
    大概就是一个用 lct 维护虚子树信息的方法

    首先是有加边操作,用 lct 会比较方便。然后想到求的那个信息,实际上就是把这个边断开以后,它的两个端点为根的两个树大小的乘积
    然后就要维护子树大小,pushup 的时候两个实儿子很好统计,加上就行,但统计虚儿子,因为对于虚儿子是“认父不认子”,所以还要对每个节点维护一个 (size2) 表示他的各个虚儿子的 (size)
    那么每次改变边的虚实,或改变一个节点的父亲(且他是这个父亲的虚儿子),就要更改 (size2)
    其实只有 accesslink 函数需要更改,access 要把 splay 完的节点的右儿子变更,这样他的原来的右儿子就成了虚儿子,要统计到他的 (size2) 里。而 link 的时候,因为连的是虚边,也要变更 (size2)
    其他的函数为什么不用变更 (size2) 写在注释里了

    还有一点,就是 link 函数一般都是只把一个点做成根,然后它连向另一个点就行,但是这里因为连边操作导致 (size) 变更,被连向的的那个点的祖先的信息也就有了变更,所以要先把被连向的那个点也做成根

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<map>
    #include<iomanip>
    #include<cstring>
    #define reg register
    #define EN std::puts("")
    #define LL long long
    inline int read(){
    	register int x=0;register int y=1;
    	register char c=std::getchar();
    	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
    	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
    	return y?x:-x;
    }
    #define N 100005
    int n,m;
    struct tr{
    	tr *fa,*son[2];
    	int size,size2;
    	int tag;
    }*null,*pos[N],dizhi[N];
    #define ident(tree,fa) (fa->son[1]==tree)
    #define pushup(tree) (tree->size=tree->son[0]->size+tree->son[1]->size+tree->size2+1)
    #define notroot(tree) (tree->fa->son[0]==tree||tree->fa->son[1]==tree)
    inline void connect(tr *tree,tr *fa,int k){fa->son[k]=tree;tree->fa=fa;}
    inline void pushdown(tr *tree){
    	if(!tree->tag) return;
    	tree->son[0]->tag^=1;tree->son[1]->tag^=1;
    	std::swap(tree->son[0],tree->son[1]);
    	tree->tag=0;
    }
    inline void rotate(tr *tree){
    	tr *fa=tree->fa,*faa=fa->fa;
    	pushdown(fa);pushdown(tree);
    	int k=ident(tree,fa);
    	connect(tree->son[k^1],fa,k);
    	tree->fa=faa;
    	if(notroot(fa)) faa->son[ident(fa,faa)]=tree;
    	//这里即使 fa 到 faa 是虚边,因为是在 fa 的子树里转,也不会影响 faa 的 size2
    	//其他的都也没有改变边的虚实,更不用改 size2
    	connect(fa,tree,k^1);
    	pushup(fa);pushup(tree);
    }
    inline void splay(tr *tree){
    	reg tr *fa,*faa;
    	while(notroot(tree)){
    		fa=tree->fa;faa=fa->fa;
    		if(notroot(fa)) ident(fa,faa)^ident(tree,fa)?rotate(tree):rotate(fa);
    		rotate(tree);
    	}
    }
    inline void access(reg tr *x){
    	for(reg tr *lastx=null;x!=null;lastx=x,x=x->fa){
    		pushdown(x);
    		splay(x);
    		x->size2+=x->son[1]->size-lastx->size;//原来的右儿子变成虚儿子
    		x->son[1]=lastx;pushup(x);
    	}
    }
    //显然 makeroot 和 split 没有改变边的虚实或其他树结构(只是调用其他函数)
    //所以也不用更新 size2
    inline void makeroot(tr *x){
    	access(x);splay(x);
    	x->tag^=1;
    }
    inline void split(tr *x,tr *y){
    	makeroot(x);
    	access(y);splay(y);
    }
    inline void link(tr *x,tr *y){
    	makeroot(x);makeroot(y);//y 的 size2 变更,导致他的祖先的信息变更,所以先把 y 也做成根
    	x->fa=y;
    	y->size2+=x->size;//x 变成 y 的虚儿子
    	pushup(y);
    }
    inline void cut(tr *x,tr *y){
    	split(x,y);
    	x->fa=y->son[0]=null;//断的是实边,不用改 size2
    	pushup(y);
    }
    inline void init(){
    	null=&dizhi[0];
    	for(reg int i=1;i<=n;i++){
    		pos[i]=&dizhi[i];
    		dizhi[i].son[0]=dizhi[i].son[1]=dizhi[i].fa=null;
    		dizhi[i].size=1;
    	}
    }
    int main(){
    	n=read();m=read();
    	init();
    	int x,y;char op;
    	while(m--){
    		op=getchar();
    		while(op!='A'&&op!='Q') op=getchar();
    		x=read();y=read();
    		if(op=='A') link(pos[x],pos[y]);
    		else{
    			cut(pos[x],pos[y]);
    			access(pos[x]);splay(pos[x]);
    			access(pos[y]);splay(pos[y]);
    //				printf("x : %d  y : %d
    ",pos[x]->size,pos[y]->size);
    			printf("%lld
    ",(long long)pos[x]->size*pos[y]->size);
    			link(pos[x],pos[y]);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    浅谈prufer编码
    数据结构训练之三
    博弈论训练之一
    动态规划训练之十三
    杂题训练之七
    奇技淫巧训练之八
    浅谈博弈论
    浅谈卡特兰数
    奇技淫巧训练之七
    浅谈概率期望的一些例题
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/13535882.html
Copyright © 2020-2023  润新知