suxxsfe

一言(ヒトコト)

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\n",pos[x]->size,pos[y]->size);
			printf("%lld\n",(long long)pos[x]->size*pos[y]->size);
			link(pos[x],pos[y]);
		}
	}
	return 0;
}
posted @ 2020-08-20 16:36  suxxsfe  阅读(150)  评论(0编辑  收藏  举报