https://www.luogu.com.cn/problem/P4219
https://darkbzoj.tk/submission/80566
(n) 个点,起初没有边,每次加一个边或查询对于一条边,有多少条不同的路径经过它,保证时刻是一个森林
大概就是一个用 lct 维护虚子树信息的方法
首先是有加边操作,用 lct 会比较方便。然后想到求的那个信息,实际上就是把这个边断开以后,它的两个端点为根的两个树大小的乘积
然后就要维护子树大小,pushup 的时候两个实儿子很好统计,加上就行,但统计虚儿子,因为对于虚儿子是“认父不认子”,所以还要对每个节点维护一个 (size2) 表示他的各个虚儿子的 (size) 和
那么每次改变边的虚实,或改变一个节点的父亲(且他是这个父亲的虚儿子),就要更改 (size2)
其实只有 access
和 link
函数需要更改,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;
}