BZOJ 4530 大融合 LCT维护子树信息

题意:

  N<=1e5个点,Q<=1e5个操作。
  支持加一条边(u,v)(保证图是森林)、询问经过边(u,v)的简单路径条数(保证(u,v)存在)。

分析:

  数据结构学傻了的我,表示并不会用树链剖分做这个题……

  首先,这道题的询问,可以看成是假设断掉某条边后,两端点的子树size相乘。(一个简单的乘法原理???)

  但是LCT模板里并没有这样的操作,那怎么办?难不成发明一个?

  (有dalao还就真的发明了一个……)

  我们的LCT中的边分为实边和虚边,假如在一棵splay树(实边)中,我们自然可以轻而易举的求出一个点的子树size——只要维护一个size数组就好了嘛。

  但是现在存在虚边,所以解决虚边问题是当务之急,那么我们就需要多维护一个数组,设siz[x]表示对于点x,经由虚边连接的子树的大小,那么在LCT的pushup操作中,我们就把一个点总的size(记为sz[x]数组)赋为两个实儿子的sz,加上虚子树的数量,加上他自己即可。(sz[x]=sz[lson]+sz[rson]+siz[x]+1)

  那么我们维护这个子树总size有什么用呢?

  考虑到在LCT中,实边和虚边是会互相转化的,即在link的过程中会增加虚边,在access过程中虚实边会相互转化,所以我们在相应位置就使用相应的策略,将两类size数组维护好即可,最终询问时,我们split(x,y),使实边存在于两点之间,这样我们只需要计算两个虚子树size相乘的结果即可!

  (祭天:我把#define rc(x) t[x][1]写成了#define rc(X) t[x][1],于是能过样例,但是Wa了一宿……)

 

 1 #include<bits/stdc++.h>
 2 #define ll long long 
 3 #define lc(x) t[x][0]
 4 #define rc(x) t[x][1] 
 5 using namespace std;
 6 const int N=100005;
 7 struct LCT{
 8     int t[N][2],siz[N],rev[N],sz[N],s[N],tp,fa[N];
 9     void pushup(int x){
10         sz[x]=sz[lc(x)]+sz[rc(x)]+siz[x]+1;
11     } void revers(int x){
12         rev[x]^=1;swap(lc(x),rc(x));
13     } void pushdown(int x){
14         if(rev[x]){ rev[x]=0;
15             if(lc(x)) revers(lc(x));
16             if(rc(x)) revers(rc(x));
17         } return ;
18     } bool pdrt(int x){
19         return rc(fa[x])!=x&&lc(fa[x])!=x;
20     } void rotate(int x){
21         int y=fa[x];int z=fa[y];
22         int dy=(rc(y)==x),dz=(rc(z)==y);
23         if(!pdrt(y)) t[z][dz]=x;
24         t[y][dy]=t[x][dy^1];fa[t[y][dy]]=y;
25         t[x][dy^1]=y;fa[y]=x;fa[x]=z;
26         pushup(y);
27     } void splay(int x){
28         s[++tp]=x;
29         for(int i=x;!pdrt(i);i=fa[i])
30         s[++tp]=fa[i];
31         while(tp) pushdown(s[tp--]);
32         while(!pdrt(x)){
33             int y=fa[x];int z=fa[y];
34             if(!pdrt(y))
35             if(rc(y)==x^rc(z)==y) rotate(x);
36             else rotate(y);rotate(x);
37         } pushup(x);return ;
38     } void access(int x){
39         for(int i=0;x;x=fa[i=x])
40         {splay(x);siz[x]+=sz[rc(x)];
41         siz[x]-=sz[rc(x)=i];}
42     } void mkrt(int x){
43         access(x);splay(x);revers(x);
44     } int fdrt(int x){
45         access(x);splay(x);
46         while(lc(x)) pushdown(x),x=lc(x);
47         return x;
48     } void split(int x,int y){
49         mkrt(x);access(y);splay(y);
50     } void link(int x,int y){
51         mkrt(x);if(fdrt(y)!=x) 
52         fa[x]=y,siz[y]+=sz[x],pushup(y);
53     } 
54 }lct;int n,m,q;
55 int main(){
56     scanf("%d%d",&n,&q);char c[10];
57     for(int i=1;i<=n;i++) lct.sz[i]=1;
58     while(q--){ int x,y;
59         scanf("%s%d%d",c,&x,&y);
60         if(c[0]=='A') lct.link(x,y);
61         else lct.split(x,y),
62         printf("%lld\n",(ll)
63         (lct.siz[x]+1)*(lct.siz[y]+1));
64     } return 0;
65 }
View Code

 

转载于:https://www.cnblogs.com/Alan-Luo/articles/10161045.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值