LCT 支持删边、加边并维护森林中每棵子树的大小

​ 本文包含大量的指代不明,纯文字乱说,没有实现代码(有了)等一切极好的要素。

​ 之前我们学过splay中有个size信息,且 s i z e [ p ] = s i z e [ l c ] + s i z e [ r c ] size[p]=size[lc]+size[rc] size[p]=size[lc]+size[rc],但这个所维护并不是子树的大小,而是splay树的大小,换言之就是splay所表示的链的长度。

​ 而现在我们要维护树的大小,但是辅助树有一个认父不认子的特性,所以只在一颗splay树中进行信息传递是不够的,我们想让他认父既认子,准确的说我们需要从虚子树中获取信息。想到虚子树的本质:一个节点某些子树的链所构成的若干棵树,为啥知识某些子树,因为还有一些子节点/子树在该节点自身所在的splay树中。

​ 所以我们为一个节点新增一个信息 s i z e 2 size2 size2,表示虚子树的节点数目之和,然后让 s i z e [ p ] = s i z e [ l c ] + s i z e [ r c ] + s i z e 2 [ p ] + 1 size[p]=size[lc]+size[rc]+size2[p]+1 size[p]=size[lc]+size[rc]+size2[p]+1,这样的话一颗splay树中单个节点的 s i z e size size虽然没有什么意义,但一颗splay树的根节点的 s i z e size size所表示的就是这颗splay树所表示的链以及所关联的子树的节点数之和,说人话就是这颗splay树中深度最小的那个节点所代表的子树的大小。然后原树的根节点所在的splay树的根节点的 s i z e size size就表示整棵树的大小。

​ 然后就是如何维护节点的 s i z e size size和 s i z e 2 size2 size2信息,那自然就是考虑对一个节点的各种操作以及修改。

  • 第一种splay树中操作,既rotate和splay操作。这两种操作都是改变一个节点在splay树中位置,显然不会影响到他的虚子树是啥,所以我们只需要按公式更改(pushUp)就行。

  • 第二种splay树之间的操作,既Access操作、link操作以及cut操作。

  • Access操作。在splay操作后会将splay的根节点的右子树变为虚子树,然后将右子树变为上一次迭代的splay树,为避免歧义将上一次迭代的splay树记为 p ′ p' p′,将当前迭代的 s p l a y splay splay树记为 p p p。而 p ′ p' p′原本是 p p p的某个节点的虚子树。这样我们发现了两处变化,一: p ′ p' p′不再是原来节点的虚子树;二: p p p原本的右子树变为虚子树,然后右子树变为了 p ′ p' p′。所以做出相应的处理即可。

    一:每次迭代后更新 p ′ p' p′的父亲。 p ’ p’ p’的父亲是谁? p p p的根节点啊,因为每次迭代我们都是用的 p ′ p' p′的父节点迭代,然后我们又将父节点splay了,所以她就成了 p p p根节点。

    二:每次迭代时更新 p p p。

    所以综合起来就是p的虚子树p’变为了实子树,而原来的实子树变为了虚子树。既两次虚实转化。

  • link操作,将两个节点加一条边,也就是让一棵树成为一个节点的子树。在LCT中的体现就是让一棵辅助树成为一个节点的虚子树。如 l i n k ( x , y ) link(x,y) link(x,y)就是将y的这棵树变为x的子树,反映在LCT上就是x和y之间建立起深度上的联系,且y的深度全部小于x,直接让y成为x的虚子树就是一种实现方法。

    具体实现上如果直接将y的辅助树作为x的虚子树,那么我们需要对x进行一次自底向上的信息更新,既所有维护了x的节点都需要更新。makeroot(y);nodes[y].fa=x;修改x的信息区;updateSize(x);

    也可以makeroot(x);splay(x);makeroot(y);然后再将y这颗辅助树加到x上。

    具体实现具体想。

    我个人觉得第一种实现更加省时间2logN大概。

  • cut操作,删除原树中的一条边。如 c u t ( x , y ) cut(x,y) cut(x,y)在LCT中体现就是就是让y这棵树子树和x之间脱离深度上的联系,也就是在splay树中使得x的右子树变为空树。cut(x,y);pushup(x);

  • tip:理解LCT的各种性质非常有助于理解LCT,以及扩展运用。

  • 对LCT进行扩展时,考虑到所有与扩展有关联的函数是一个不错的实现方法。

    以下总结摘自oi-wiki

    总结一下 LCT 维护子树信息的要求与方法:

    1. 维护的信息要有 可减性 ,如子树结点数,子树权值和,但不能直接维护子树最大最小值,因为在将一条虚边变成实边时要排除原先虚边的贡献。

    2. 新建一个附加值存储虚子树的贡献,在统计时将其加入本结点答案,在改变边的虚实时及时维护。

    3. 其余部分同普通 LCT,在统计子树信息时一定将其作为根节点。

    4. 如果维护的信息没有可减性,如维护区间最值,可以对每个结点开一个平衡树维护结点的虚子树中的最值。

      个人的理解:

      对于第一点的可减性主要体现在一个节点所维护的虚子树信息上面,比如虚子树的大小具有可减性,但虚子树中的最值并不具有O(1)时间内就可以完成可减性。

      对于第二点就是一个节点新增一个信息区表示虚子树的信息综合,然后使用这个虚子树信息区完成对自身其他信息的更新。以及需要关注虚实边的变化,对应更改相应的函数。

      第三点。之前提到过的,splay树中的一个节点所维护的是其部分子树的信息,另一部分在这棵splay树的其他节点中得到体现,但如果可以保证这棵splay树中没有其子树中的节点,那么就可以通过这个节点直接获得子树信息。在实现上就是ACCESS(x)即可,然后直接使用x的信息,就可以得到x这棵子树的信息。也可以临时将这颗子树与其父节点之间的边删除(指的是原树的父亲,不是splay树上的父亲)。也可以让这个节点成为根节点,然后用左子树的信息和该节点的信息进行计算得到结果。个人倾向于第一种实现方法。

      第四点非常神奇,解决了不具有可减性的信息的维护,又是可爱的该死的树套树。以最值举个例子,支持删除的情况下获取最值最朴素的方法就是O(N)的遍历它的所有虚子树,但是我们可以将所有虚子树的最值插入平衡树中,这样我们可以用O(N)的空间获得O(logN)的插入删除以及查询操作来维护虚子树的最值。

#include <bits/stdc++.h>
using namespace std;
struct lctNode{int fa;int ch[2];//add your infoint sz,sz2;//add your tagbool rotatetag;
};
struct LCT{static const int MAXN=100000+10;lctNode nodes[MAXN];//the node you can use in the lctint n;//get a node's kindinline int Get(int x) {return nodes[nodes[x].fa].ch[1] == x;}//judge a node is the root in the splay treeinline bool isRoot(int x) {return nodes[nodes[x].fa].ch[0] != x && nodes[nodes[x].fa].ch[1] != x;}//if a node's child tree'struct change,use this to update infovoid pushUp(int x) {nodes[x].sz = nodes[nodes[x].ch[0]].sz + nodes[nodes[x].ch[1]].sz + nodes[x].sz2 + 1;}//pushDonw'son functionvoid pushRotate(int p){if(!p)return;swap(nodes[p].ch[0],nodes[p].ch[1]);nodes[p].rotatetag^=1;}//if you need visit a nodes'son,use this to make it realvoid pushDown(int x){if(nodes[x].rotatetag) {pushRotate(nodes[x].ch[0]);pushRotate(nodes[x].ch[1]);nodes[x].rotatetag = false;}}//if you want ope on node which is unreal,please use it to make it realvoid update(int x) {if (!isRoot(x))update(nodes[x].fa);pushDown(x);}//rotate xvoid rotate(int x){int y=nodes[x].fa;int z=nodes[y].fa;int k=Get(x);if(!isRoot(y)) nodes[z].ch[Get(y)]=x;nodes[x].fa=z;nodes[nodes[x].ch[!k]].fa=y;nodes[y].ch[k]=nodes[x].ch[!k];nodes[x].ch[!k]=y;nodes[y].fa=x;pushUp(y);pushUp(x);//we don't need to pushUP z,because it's son'stuct change don't produce a change to z}//splay a node to the splay rootvoid splay(int x) {//before we ope on x,we need make sure it's trueupdate(x);//rotate twice every route,the first rotate isn't necessaryfor (int p = nodes[x].fa; !isRoot(x); p = nodes[x].fa) {if (!isRoot(p))rotate(Get(p) == Get(x) ? p : x);rotate(x);}}//this function is used to produce a splay from the root in the origin tree to the node x//and it will return the splay rootint Access(int x) {int p = 0;while (x) {//this cut is on the splay tree so don't cut the edge from son to fathersplay(x);//use this to modify the sz2nodes[x].sz2+=nodes[nodes[x].ch[1]].sz-nodes[p].sz;nodes[x].ch[1] = p;//we delete the on son tree of x,so it's necessary to pushUp x//use this to modify the szpushUp(x);p = x;x = nodes[x].fa;}return p;}//this function is used to make the node x to the root in the origin tree//this function will return a splay tree's root which have node xint makeRoot(int x) {x = Access(x);//add a rotate tag to xswap(nodes[x].ch[0], nodes[x].ch[1]);nodes[x].rotatetag ^= 1;return x;}//this function is used to add a edge between x and y in the origin tree//but you need make sure x and y isn't at the same treevoid link(int x,int y) {//we must make x to the root else this edge mean the preroot between and yx = makeRoot(x);makeRoot(y);//try optimization it's//splay(x);nodes[x].fa = y;//use this to modify the sz2 of the ynodes[y].sz2 += nodes[x].sz;while (y) {pushUp(y);y = nodes[y].fa;}}//this function will produce a splay tree from x to y in the origin tree path//it will return the splay root,but this root can be not x or yint split(int x,int y){makeRoot(x);return Access(y);}//this function is used to cut edge x-y//but you need make sure there is a edge between x and yint res1,res2;void cut (int x,int y) {makeRoot(x);Access(y);splay(x);nodes[y].fa = 0;nodes[x].ch[1] = 0;pushUp(x);res1 = nodes[x].sz;res2 = nodes[y].sz;}//this function is used to find the root in the origin treeint find(int x) {x = Access(x);while (nodes[x].ch[0])x = nodes[x].ch[0];//if we ope a node in the splay tree,after that make it to the root of the splaysplay(x);return x;}//this function is used to test if there a edge between x and ybool haveEdge(int x,int y){if(find(x)==find(y)){makeRoot(x);Access(y);splay(x);if(nodes[x].ch[1]==y&&nodes[y].ch[0]==0)return true;}return false;}//this ope is used to init the LCT//please write a empty tree in this place//every change on empty tree will useless//every use of empty tree will truevoid init() {nodes[0].fa = 0;nodes[0].ch[0] = nodes[0].ch[1] = 0;nodes[0].rotatetag = false;n = 1;//other init openodes[0].sz=0;nodes[0].sz2=0;}//this function let everynodes be empty treevoid clear() {for (int i = 1; i < n; i++) {nodes[i].rotatetag = false;nodes[i].fa = 0;nodes[i].ch[0] = nodes[i].ch[1] = 0;//other clear operation}n = 1;}//this function is used to produce a newTree according your infoint newTree(int _weight) {//use index: nreturn n++;}//your special function}tree;
int main() {int n, q;tree.init();scanf("%d%d", &n, &q);for (int i = 1; i <= n; i++) {tree.nodes[i].sz = 1;}char s[10];int x, y;for (int i = 1; i <= q; i++) {scanf("%s", s);scanf("%d%d", &x, &y);if (strcmp(s, "A") == 0) {tree.link(x, y);} else {tree.cut(x, y);printf("%lld\n", (long long) tree.res1 * tree.res2);tree.link(x, y);}}return 0;
}

LCT 支持删边、加边并维护森林中每棵子树的大小相关推荐

  1. ME60单板加载故障维护经验

    ME60单板加载故障维护经验 加载是设备管理中重要的模块.它完成系统软件和逻辑软件从主控板的 CFcard下载到接口板或者交换网板的存储区域.接口板或者交换网板的存储区域有以下三种: 1  单板 CF ...

  2. android 刷新某条数据_Android 支持刷新、加载更多、带反弹效果的RecyclerView

    点击上方"Android技术杂货铺",选择"标星" 干货文章,第一时间送达! 开篇 当前市面上很多支持刷新.加载更多RecyclerView开源库,为何我这里还 ...

  3. [html] HTML5的video怎样预加载(支持全量加载)?

    [html] HTML5的video怎样预加载(支持全量加载)? preload="auto" 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷.欢迎 ...

  4. 用ffmpeg修改MP4文件头信息,使其支持流式加载及播放

    经常有用户反映,有些网页中加载的mp4文件,有的可以加载一点就开始播放,有的就必须全部加载完才能播. 经核实,主要是头信息的数据顺序有关,用工具:mp4info.exe可以查看mp4文件的结构信息: ...

  5. 使用HTML+CSS实现网页loading加载效果,支持定时或加载完成后隐藏

    网页使用loading可以给用户带来更好的体验,避免网页渲染中长时间出现网页整体空白从而影响访客的体验,loading在部分大型APP也有在应用. 下面使用HTML+CSS+JS实现完整的Loadin ...

  6. 支持通话/音量加减/接听功能TypeC线控耳机方案开发

    SSS1531,SSS1530鑫创研发支持通话/音量加减/接听功能TypeC线控耳机方案,由于各手机系统兼容性不同,各品牌USB音频芯片也无法做到全兼容各手机型号,之前台湾鑫创已出SSS1530+MC ...

  7. 一行实现QQ群组头像,微信群组,圆角等效果. 并支持url直接加载图片

    说点题外话. Coding中我们总是经历着这么几个过程. 学会使用: 不管是API也好, 开源库也好. 总是在最开始的学会去用. 了解实现原理: 可能会因为一些不兼容, 代码的异常状态的处理不够完美等 ...

  8. mysql驱动为什么自动加载_为什么JDBC中加载驱动要使用反射?

    原文链接:https://www.cnblogs.com/homejim/p/8076481.html 在JDBC详解系列(一)之流程中,我将数据库的连接分解成了六个步骤. JDBC流程: 第一步:加 ...

  9. 将服务器文件加载至hive表中,Hive入门到剖析(四)

    10 Hive体系架构 10.1概念 用户接口:用户访问Hive的入口 元数据:Hive的用户信息与表的MetaData 解释器:分析翻译HQL的组件 编译器:编译HQL的组件 优化器:优化HQL的组 ...

最新文章

  1. Ferderweisser
  2. 【视频课】永久免费!5小时快速掌握Pytorch框架入门及实战
  3. 力扣——有序链表转换二叉搜索树
  4. Maven(九)Eclipse创建Web项目(简单方式)
  5. 自定义线程池-线程类和任务类代码实现
  6. HTML5-基础语法
  7. 安卓应用安全指南 5.1 创建密码输入界面
  8. python 提取网页正文_一篇文章教会你用Python爬取淘宝海量信息,把淘宝商品整理成一个表格...
  9. mysql创建表的默认大小_mysql InnoDB建表时设定初始大小的方法
  10. read()/write()的生命旅程之二——第二章:read()
  11. DateUtils工具类
  12. 不只是槓杆原理~~细说油压煞车
  13. 开启并定制 Apache 显示目录索引样式
  14. npm安装ionic相关设置
  15. excel两列数据对比找不同_Excel快速核对数据,不用函数这招让你3秒搞定,建议收藏...
  16. My console windows won't go away
  17. AD(活动目录)中组的类型与工作范围
  18. 计算机图形学(一) 视频显示设备_7_光栅扫描系统
  19. 安卓开发 之小白养成-Android适配
  20. JAVA看云判断天气_怎样看云判断天气变化?

热门文章

  1. 获取铁矿石和螺纹钢期货数据。对收益率序列进行描述性统计、jb检验,反正是否符合分形市场假说。计算Hurst指数,制定跨品种套利策略,并进行回测,对跨品种套利效果进行评估。寻求改进空间。
  2. python 批处理yolo标注的图像 图像与标签同步处理
  3. codeforces 538B
  4. 旧金山全美首禁售电子烟,电子烟风口还能保持多久?
  5. Android图片加载库的封装实战
  6. nowcoder 区区区间间间 (单调栈)
  7. ubuntu base文件系统移植
  8. zblog在线投稿php,简单免费的ZBLOGPHP投稿插件
  9. 区块链价值不仅是技术层面的创新与颠覆,还要看到分布式信任机制带来的组织管理模式等创新
  10. 诚之和:使用Java+Swing实现医院管理系统的实战练习 附完整实例代码