[BJOI2014]大融合(Link Cut Tree)

题面

给出一棵树,动态加边,动态查询通过每条边的简单路径数量。

分析

通过每条边的简单路径数量显然等于边两侧节点x,y子树大小的乘积。

我们知道裸的LCT只能维护链的信息,那么怎么维护子树大小呢?我们只需要对于节点x维护x的所有虚儿子的子树大小之和vir。那么查询的时候先split(x,y),这样x到y就成为了实链,其他与x相连的节点都是虚儿子。那么x一侧的子树大小就是vir[x]+1,y一侧的子树大小就是vir[y]+1

考虑虚子树大小如何维护:

首先总的子树大小可以在push_up的时候维护,sz[x]=sz[lson(x)]+sz[rson(x)]+vir[x]+1.相当于把实儿子的子树大小求和,然后加上虚儿子的子树大小之和,再加上本身1

注意到只有access和link操作会影响到vir[x].

//一般LCT的access
void access(int x){
for(int y=0;x;y=x,x=fa(x)){
splay(x);
rson(x)=y;
push_up(x);
}
}

观察access的过程,我们发现原来x的右儿子变成了虚儿子,而y变成了x的实儿子。因此vir要加上原来rson(x)的子树大小,并且减去y的子树大小

void access(int x){
for(int y=0;x;y=x,x=fa(x)){
splay(x);
tree[x].vir+=tree[rson(x)].sz;//原来的实儿子变成虚儿子
rson(x)=y;
tree[x].vir-=tree[rson(x)].sz;//原来的虚儿子变成实儿子
push_up(x);
}
}

link操作更简单

//一般LCT的link
void link(int x,int y){
make_root(x);
fa(x)=y;
push_up(y);
}

直接把vir[y]加上sz[x]即可

void link(int x,int y){
split(x,y);
fa(x)=y;
tree[y].vir+=tree[x].sz;
push_up(y);
}

注意到这里必须split(x,y),因为split之前y在原树中不一定是根,link了以后y的所有祖先的sz数组都是需要更新的.所以要splay(y),把sz先更新一下。否则会造成一些节点的sz未被更新.

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100000
using namespace std;
typedef long long ll;
struct LCT{
#define lson(x) (tree[x].ch[0])
#define rson(x) (tree[x].ch[1])
#define fa(x) (tree[x].fa)
struct node{
int ch[2];
int fa;
int sz;//总的子树大小
int vir;//除了实链外连接到x的点的子树大小
int revm;
}tree[maxn+5];
inline bool is_root(int x){
return !(lson(fa(x))==x||rson(fa(x))==x);
}
inline int check(int x){
return rson(fa(x))==x;
}
void push_up(int x){
tree[x].sz=tree[lson(x)].sz+tree[rson(x)].sz+1+tree[x].vir;
}
void reverse(int x){
swap(lson(x), rson(x));
tree[x].revm^=1;
}
void push_down(int x){
if(tree[x].revm){
reverse(lson(x));
reverse(rson(x));
tree[x].revm=0;
}
}
void push_down_all(int x){
if(!is_root(x)) push_down_all(fa(x));
push_down(x);
}
void rotate(int x){
int y=fa(x),z=fa(y),k=check(x),w=tree[x].ch[k^1];
tree[y].ch[k]=w;
tree[w].fa=y;
if(!is_root(y)) tree[z].ch[check(y)]=x;
tree[x].fa=z;
tree[x].ch[k^1]=y;
tree[y].fa=x;
push_up(y);
push_up(x);
}
void splay(int x){
push_down_all(x);
while(!is_root(x)){
int y=fa(x);
if(!is_root(y)){
if(check(x)==check(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
push_up(x);
}
void access(int x){
for(int y=0;x;y=x,x=fa(x)){
splay(x);
tree[x].vir+=tree[rson(x)].sz;//原来的实儿子变成虚儿子
rson(x)=y;
tree[x].vir-=tree[rson(x)].sz;//原来的虚儿子变成实儿子
push_up(x);
}
}
void make_root(int x){
access(x);
splay(x);
reverse(x);
}
void split(int x,int y){
make_root(x);
access(y);
splay(y);
}
void link(int x,int y){
split(x,y);
fa(x)=y;
tree[y].vir+=tree[x].sz;
push_up(y);
}
void cut(int x,int y){
split(x,y);
lson(y)=fa(x)=0;
push_up(y);
}
ll query(int x,int y){
split(x,y);
// return 1ll*(tree[x].sz)*(tree[y].sz);
return 1ll*(tree[x].vir+1)*(tree[y].vir+1);
}
}T; int n,m;
int main(){
int u,v;
char cmd[2];
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) T.tree[i].sz=1;
for(int i=1;i<=m;i++){
scanf("%s %d %d",cmd,&u,&v);
if(cmd[0]=='A'){
T.link(u,v);
}else{
// T.cut(u,v);
printf("%lld\n",T.query(u,v));
// T.link(u,v);
}
}
}

最新文章

  1. mysql 联合索引和唯一索引
  2. QuickHit快速击键小程序 --S2.4.5
  3. 用PHP+H5+Boostrap做简单的音乐播放器(进阶版)
  4. ios - block数据的回调
  5. WINDOWS下用脚本运行redis和mongodb
  6. HTML&lt;marquee&gt;标签
  7. 四轴飞行diy全套入门教程(从最基础的开始)
  8. msSQL数据库备份还原小结
  9. GPS坐标转百度地图并且加载地图示例.支持微信端访问
  10. [Cocos2d-x]Cocos2d-x 3.2 学习笔记
  11. MVC 创建Word文档
  12. XCode消除警告、错误
  13. SSH整合配置文件概括
  14. 小tips:使用rem+vw实现简单的移动端适配
  15. 挖矿病毒、ddos入侵流程及溯源
  16. PyQt5之布局管理
  17. 容器—stack
  18. http中的filter拦截servlet之后获取body,字符流关闭,无法继续传入控制器
  19. hive笔记:复杂数据类型-array结构
  20. 【原创】Linux基础之chkconfig systemd

热门文章

  1. jQuery事件之一次性事件
  2. PHP依赖管理工具Composer入门
  3. 【Python】模块学习之locust性能测试
  4. 关于mysql创建数据库,基字符集 和 数据库排序规则 的对比选择
  5. CSS效果——绝对居中
  6. Python中函数的使用
  7. [django]update_or_create使用场景
  8. Hibernate HelloWorld案例
  9. Mysql强制修改密码
  10. Django使用消息提示简单的弹出个对话框