题目描述
题目大意:一个图,动态加边。保证不会出现环,问你一条边被多少条路径经过。点数和询问数不超过10^5.
思路
LCT维护子树大小。
每个LCT上的点,维护其全子树大小sum_siz,虚子树(虚边连出去的子树)大小siz。
一个点本身的大小算进siz里,在Splay过程中维护sum_siz,即sum_siz=son[0]->sum_siz+son[1]->sum_siz+siz。对于全子树大小的维护场合跟一般的Up是一样的。
对于siz的维护,在改变边的虚实和建立虚边时维护,就是在Access和Link时维护。当Access砍掉右儿子的时候siz+=son[1]->sum_siz,接上x时siz-=x->sum_siz。
Link的时候要特别注意,如果直接Evert(x),修改y的siz是不行的。因为如果y不是根的话,y在原树里的祖先的sum_siz也是要维护的。这样不方便,我们不妨将y也Evert到它所在的原树的根,再令y->siz+=x->sum_siz,就没有问题了。
维护子树的操作大概就是这样,根据我个人的幼稚理解,我们要拿一个点的子树信息,只有当其处于原树的根(Evert后)再Splay到根或其所在的Splay所维护的链上的最深处(Access后)才能得到正确的答案。
因为我们维护sum_siz是在Splay上维护的,我们只能拿到其Splay上左右子树的全子树及虚子树信息,只有当前点为原树的根且为Splay的根时才能保证其全子树信息与原树子树信息一致。同理,当一个点为一条从根往下的链的末端时,它的虚子树信息就是原子树信息。(然而这题将siz写成sum_siz交了居然A了??难道是我理解错了??)
总之,在拿全子树信息(或实边信息)时要Splay,其余情况不用。
胡扯了那么久,其实题解就一句话。容易发现,一条边的答案就是边两侧的点数的乘积。这个用子树大小就能算出来。
类似的,用这种方法可以维护子树可加减的其他信息。(比如异或和之类的)
代码
#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
int n, q, cnt;
char s[5];
struct Tnode{
Tnode *son[2], *fa;
int parent, siz, sum_siz;
bool rev;
int Get_d(){return fa->son[1] == this;}
void Connect(Tnode *now, int d){(son[d] = now)->fa = this;}
void Up(){
sum_siz = siz;
if(son[0]) sum_siz += son[0]->sum_siz;
if(son[1]) sum_siz += son[1]->sum_siz;
}
void Down(){
if(rev){
swap(son[0], son[1]);
if(son[0]) son[0]->rev ^= 1;
if(son[1]) son[1]->rev ^= 1;
rev = false;
}
}
}tree[maxn], *Tr[maxn];
Tnode *NewTnode(){
tree[cnt].son[0] = tree[cnt].son[1] = tree[cnt].fa = NULL;
tree[cnt].siz = tree[cnt].sum_siz = 1;
tree[cnt].parent = 0;
tree[cnt].rev = false;
return tree+cnt++;
}
void Zig(Tnode *now){
Tnode *last = now->fa;
int d = now->Get_d();
if(now->son[!d]) last->Connect(now->son[!d], d);
else last->son[d] = NULL;
if(last->fa) last->fa->Connect(now, last->Get_d());
else now->fa = NULL;
now->Connect(last, !d);
now->parent = last->parent;
last->parent = 0;
last->Up();
}
void Splay(Tnode *now){
Tnode *last;
while(now->fa){
last = now->fa;
if(last->fa) last->fa->Down();
last->Down(); now->Down();
if(last->fa) (now->Get_d() ^ last->Get_d()) ? Zig(now) : Zig(last);
Zig(now);
}
if(!now->fa) now->Down();
now->Up();
}
void Change(int x){
Splay(Tr[x]);
if(Tr[x]->son[1]){
Tr[x]->siz += Tr[x]->son[1]->sum_siz;
Tr[x]->son[1]->fa = NULL;
Tr[x]->son[1]->parent = x;
Tr[x]->son[1] = NULL;
Tr[x]->Up();
}
}
void Access(int x){
Change(x);
int y = Tr[x]->parent;
for(; y; x = y, y = Tr[x]->parent){
Change(y);
Tr[y]->Connect(Tr[x], 1);
Tr[y]->siz -= Tr[x]->sum_siz;
Tr[y]->Up();
Tr[x]->parent = 0;
}
}
void Evert(int x){
Access(x);
Splay(Tr[x]);
Tr[x]->rev ^= 1;
}
void Link(int x, int y){
Evert(x);
Evert(y);
Tr[x]->parent = y;
Tr[y]->siz += Tr[x]->sum_siz;
}
long long Query(int x, int y){
Evert(x);
Access(y);
Splay(Tr[x]);
return 1LL * Tr[y]->siz * (Tr[x]->sum_siz - Tr[y]->siz);
}
int main(){
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; i++) Tr[i] = NewTnode();
int x, y;
while(q --){
scanf("%s%d%d", s, &x, &y);
if(s[0] == 'A') Link(x, y);
else printf("%lld\n", Query(x, y));
}
return 0;
}
犹如昼去夜来