关于树论【动态树问题(LCT)】
搬运:看一道caioj1439
题目描述
一开始给你一棵n个点n-1条边的树,每个点有一个权值wi。
三种操作:
op=1 u v :在点u和点v之间建一条边。
op=2 u v:摧毁点u到点v之间的边。
op=3 w u v:将点u和点v之间路径上的点(包括u,v),权值增加w。
op=4 u v:询问点u到点v之间路径上的点(包括u,v),权值最大值。
当操作违法时(询问一中u,v已经相连,二三四中u,v不联通,一二操作u==v)不进行操作并输出-1。
输入
从文件weight.in中读取输入。
第1行为1个正整数n,表示点的个数。
第2~n行为开始树所有的边,每行两个正整数u,v,代表u和v之间有一条边。
第n+1行有n个正整数,表示一开始点的权值。
下一行为一个正整数m代表下来有m个操作。
以下m行,一行表示一个操作。
行首先输入一个正整数op。
当op=1,2,4时,输入两个正整数u,v
当op=3时,输入三个正整数w,u,v
操作如题意
输出
输出到文件weight.out。
对每个4操作,输出点u到点v之间路径上的点(包括u,v),权值最大值。同时对于违法情况输出-1。
该类动态树问题一个突出点就是动态,假如没有1、2操作当然可以方便的运用树链剖分算法水过(详见第8章 树链剖分)。前一章的伸展树只支持改变树的形态,难以对树的结构进行改变,对于建边删边的操作的需要,我们要运用多棵伸展树组成新树,即解决该类动态树问题的普遍方法,Link-Cut-Tree,俗称LCT。
它跟树链剖分类似,只不过树剖用线段树维护重链,而LCT用伸展树(是不是很高大上),在两棵伸展树之间,如果它们属于同一个LCT,那么将有一条虚边,连接着它们,在不影响伸展树的正常操作前提上,保持应有的连系。
大家可以感性的认识….可以假设一开始问题给出的树边都是虚边,我们人为的在上面画重链,每条重链用一棵伸展树维护他(就跟线段树一个道理嘛,目标是减少暴力枚举的时间,只不过伸展树更加快捷灵活),关键的,如果没有连边删边操作,同伸展树一样,整棵树的结构是不变的。
当然啦,题目也可能给出很多棵树,我们可以臆想一下,这些树都属于0节点的子树,只不过他们连的边被“操作删除”了,这样也是合理的。同样道理,当我们在解决动态树问题的过程中,有时也会出现这棵树被分成多份。也就是说,Link-Cut-Tree本质上这个图可以是一个森林。
讲讲操作吧。
最重要的access(x):令x到当前所处的树的根这条路径成为偏爱路径(相当于树剖的重链),然后用splay维护,这是与树剖最大的不同,这样的灵活性也符合动态树。
make_root(x):令x成为当前树的根,但是!!不是在当前重链中伸展树的根,也不是整个图中所有点的根,而是,x当前所处的树的根! 由于 LCT的Link和Cut操作,注定了整个图可能出现多棵树,树与树之间如果不添加边,都是一个独立的动态树。
Link(x,y):让x成为根,然后连一条虚边到y就OK了。
Cut(x,y):先将x设为根(假设现在是点1)假设y是点6,那我们将1~6的路径设为偏爱路径(放在一棵伸展树里)将6旋转到伸展树的根,可以发现,点1肯定在伸展树的最左端,让y断开与左端的连接就行了。
findroot(x):同理,真正在树中的根肯定在树的最左边,所以说找根其实很简单。
PS:所以在make_root后要让整棵伸展树翻转,比如说将6变为根,1,4都在它左边,这样就不科学了。
如果还有不明白的,可以看caioj的书,还有上caioj1439看视频,视频非常好!!出视频的人改变了我的一生,从未见过有如此懂我的人,他太强了,我崇拜他一辈子!!
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
struct node
{
int f,d,c,n,son[],mx,ad;
bool fz;
}tr[];
void add(int x)
{
tr[x].d+=tr[x].ad;tr[x].mx+=tr[x].ad;
int lc=tr[x].son[],rc=tr[x].son[];
tr[lc].ad+=tr[x].ad;
tr[rc].ad+=tr[x].ad;
tr[x].ad=;
}
void update(int x)
{
int lc=tr[x].son[],rc=tr[x].son[];
tr[x].c=tr[lc].c+tr[rc].c+tr[x].n;
if(tr[lc].ad!=)add(lc);
if(tr[rc].ad!=)add(rc);
if(lc==)tr[lc].mx=;
if(rc==)tr[rc].mx=;
tr[x].mx=max(max(tr[lc].mx,tr[rc].mx),tr[x].d);
}
void reverse(int x)
{
tr[x].fz=false;
swap(tr[x].son[],tr[x].son[]);
int lc=tr[x].son[],rc=tr[x].son[];
tr[lc].fz=-tr[lc].fz;
tr[rc].fz=-tr[rc].fz;
}
void rotate(int x,int w)
{
int f=tr[x].f,ff=tr[f].f;
int R,r; R=f;r=tr[x].son[w];
tr[R].son[-w]=r;
if(r!=)tr[r].f=R; R=ff;r=x;
if(tr[R].son[]==f)tr[R].son[]=r;
else if(tr[R].son[]==f)tr[R].son[]=r;
tr[r].f=R; R=x;r=f;
tr[R].son[w]=r;
tr[r].f=R; update(f);
update(x);
}
int tmp[];
void splay(int x,int rt)
{
int s=,i=x;
while(tr[i].f!=&&(tr[tr[i].f].son[]==i||tr[tr[i].f].son[]==i))
{
tmp[++s]=i;
i=tr[i].f;
}
tmp[++s]=i;
while(s!=)
{
i=tmp[s];s--;
if(tr[i].fz==true)reverse(i);
if(tr[i].ad!=)add(i);
} while(tr[x].f!=rt&&(tr[tr[x].f].son[]==x||tr[tr[x].f].son[]==x))//还有虚边啊!
{
int f=tr[x].f,ff=tr[f].f;
if(ff==rt||(tr[ff].son[]!=f&&tr[ff].son[]!=f))
{
if(x==tr[f].son[])rotate(x,);
else rotate(x,);
}
else
{
if(tr[f].son[]==x&&tr[ff].son[]==f){rotate(f,);rotate(x,);}
else if(tr[f].son[]==x&&tr[ff].son[]==f){rotate(x,);rotate(x,);}
else if(tr[f].son[]==x&&tr[ff].son[]==f){rotate(x,);rotate(x,);}
else if(tr[f].son[]==x&&tr[ff].son[]==f){rotate(f,);rotate(x,);}
}
}
}
int n,w[];
void make_tree()
{
for(int i=;i<=n;i++)
{
tr[i].f=;
tr[i].mx=tr[i].d=w[i];
tr[i].c=;tr[i].n=;
tr[i].son[]=tr[i].son[]=;
tr[i].fz=false;tr[i].ad=;
}
}
void access(int x)//访问x
//还记得树剖的重儿子吗?这是令点x到整棵动态树的根这条路径变成偏爱路径(相当于树剖的重链),这一条路径就是一棵伸展树。
{
int y=;
while(x!=)
{
splay(x,);
tr[x].son[]=y;
if(y!=)tr[y].f=x;
y=x;x=tr[x].f;
}
}
void makeroot(int x)//让x成为当前树的根
{
access(x);splay(x,);//因为是链,splay之后只有左孩子(上面y=0)
tr[x].fz=-tr[x].fz;//因为要让x成为整棵树的根,所以x的深度要最小(通过翻转实现),为find_root做准备
}
void link(int x,int y)
{//为什么可以直接用makeroot??因为判断过x和y的find_root 是否相同,不相同表示x和y是不联通的
makeroot(x);tr[x].f=y;access(x);//删去access是没有影响的,但从定义上说应该加上
}
void cut(int x,int y)
{
makeroot(x);
access(y);splay(y,);
tr[tr[y].son[]].f=;tr[y].son[]=;
update(y);
}
int find_root(int x)//访问完x后,x所属的伸展树的最左端的点就是所在树真正的根,因为伸展树实际意义上就是一条链啊!!
{
access(x);splay(x,);
while(tr[x].son[]!=)x=tr[x].son[];
return x;
}
void increase(int x,int y,int W)//令x,y处于一棵伸展树,y为根,由于是链,直接更新y的ad就行了
{
makeroot(x);
access(y);splay(y,);
tr[y].ad+=W;
}
int findmax(int x,int y)//同理,这也是一样的
{
makeroot(x);
access(y);splay(y,);
update(y);return tr[y].mx;
}
struct edge
{
int x,y;
}e[];
int main()
{
freopen("weight.in","r",stdin);
freopen("weight.out","w",stdout);
int m,op,x,y,W;
while(scanf("%d",&n)!=EOF)
{
for(int i=;i<n;i++)scanf("%d%d",&e[i].x,&e[i].y);
for(int i=;i<=n;i++)scanf("%d",&w[i]);
make_tree();
for(int i=;i<n;i++)
link(e[i].x,e[i].y);
scanf("%d",&m);
for(int i=;i<=m;i++)
{
scanf("%d",&op);
if(op==)
{
scanf("%d%d",&x,&y);
if(find_root(x)==find_root(y)||x==y)
printf("-1\n");
else
link(x,y);
}
else if(op==)
{
scanf("%d%d",&x,&y);
if(find_root(x)!=find_root(y)||x==y)
printf("-1\n");
else
cut(x,y);
}
else if(op==)
{
scanf("%d%d%d",&W,&x,&y);
if(find_root(x)!=find_root(y))
printf("-1\n");
else
increase(x,y,W);
}
else
{
scanf("%d%d",&x,&y);
if(find_root(x)!=find_root(y))
printf("-1\n");
else
printf("%d\n",findmax(x,y));
}
}
printf("\n");
}
return ;
}
关于树论【动态树问题(LCT)】的更多相关文章
- zTree静态树与动态树的用法——(七)
0.[简介] zTree 是利用 JQuery 的核心代码,实现一套能完成大部分常用功能的 Tree 插件 兼容 IE.FireFox.Chrome 等浏览器 在一个页面内可同时生成多个 Tree 实 ...
- 【zTree】zTree的3.5.26静态树与动态树(实用)
1.静态树: 目录结构:(css与js为下载的原文件夹)
- luogu3703 [SDOI2017]树点涂色(线段树+树链剖分+动态树)
link 你谷的第一篇题解没用写LCT,然后没观察懂,但是自己YY了一种不用LCT的做法 我们考虑对于每个点,维护一个fa,代表以1为根时候这个点的父亲 再维护一个bel,由于一个颜色相同的段一定是一 ...
- 点分治Day2 动态树分治
蒟蒻Ez3real冬令营爆炸之后滚回来更新blog... 我们看一道题 bzoj3924 ZJOI2015D1T1 幻想乡战略游戏 给一棵$n$个点的树$(n \leqslant 150000)$ 点 ...
- 动态树之LCT(link-cut tree)讲解
动态树是一类要求维护森林的连通性的题的总称,这类问题要求维护某个点到根的某些数据,支持树的切分,合并,以及对子树的某些操作.其中解决这一问题的某些简化版(不包括对子树的操作)的基础数据结构就是LCT( ...
- 动态树LCT小结
最开始看动态树不知道找了多少资料,总感觉不能完全理解.但其实理解了就是那么一回事...动态树在某种意思上来说跟树链剖分很相似,都是为了解决序列问题,树链剖分由于树的形态是不变的,所以可以通过预处理节点 ...
- bzoj2049-洞穴勘测(动态树lct模板题)
Description 辉辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好 ...
- hdu 5398 动态树LCT
GCD Tree Time Limit: 5000/2500 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Su ...
- hdu 5002 (动态树lct)
Tree Time Limit: 16000/8000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submi ...
- BZOJ2759 一个动态树好题 LCT
题解: 的确是动态树好题 首先由于每个点只有一个出边 这个图构成了基环内向树 我们观察那个同余方程组 一旦形成环的话我们就能知道环上点以及能连向环上点的值是多少了 所以我们只需要用一种结构来维护两个不 ...
随机推荐
- 用 Jackson 来处理 JSON
Jackson 是一个 Java 用来处理 JSON 格式数据的类库,性能非常好. 首先创建一个User对象类 (User.java) package com.sivalabs.json; impor ...
- 高通android7.0刷机工具使用介绍
刷机工具安装 1. 安装QPST.WIN.2.7 Installer-00448.3 2. 安装python2.7,并配置其环境变量 刷机方法 1.将编译后的刷机文件拷贝到如下目录:SC20_CE_p ...
- POSTMAN编写文档
第一步:创建文件夹: 同时创建全局变量: 第二步:创建分组文件夹: 第三步:添加请求: 类似正常调试,然后多了一步保存: 保存: 请求方式发生相应变化,同时颜色也发生变化,说明保存成功: ====== ...
- WEB学习-CSS基础选择器
基础选择器 标签选择器 就是标签的名字. • <h1>前端与移动开发<span>1期班</span>基础班</h1> css: • <style ...
- (44)C#网络2
一.用SmtpClient类发送邮件 允许应用程序使用简单邮件传输协议 (SMTP) 发送电子邮件 using System.Net.Mail; SmtpClient smtpClient = new ...
- JVM内存分为哪几部分?各个部分的作用是什么?
JVM内存分为哪几部分?各个部分的作用是什么? 1. Java虚拟机内存的五大区域 Java的运行离不开Java虚拟机的支持,今天我就跟大家探讨一下Java虚拟机的一些基础知识. JVM内存区域分 ...
- List 与 ArrayList 的使用
最近回顾 java 集合,发现大部分程序中都在使用 List list = new ArrayList(); 也有部分程序使用 ArrayList list = new ArrayList(); 那么 ...
- java验证身份证号码是否有效源代码 wn25的头像 wn25 23 2015-01-04 20:09 6 基本信息 Java × 1 浏览
原文:http://www.open-open.com/code/view/1420373343171 1.描述 用java语言判断身份证号码是否有效,地区码.出身年月.校验码等验证算法 2.源代码 ...
- 【Todo】【转载】Scala中Array, List, Tuple的区别
参考了这篇文章: https://my.oschina.net/u/1034176/blog/512314 1. 在Scala 2.7中,Array.List都不能混合类型,只有Tuple可以:而在S ...
- 自主学习Flappy Bird游戏
背景 强化学习 MDP基本元素 这部分比较难懂,没有详细看:最优函数值,最优控制等 Q-learning 神经网络 环境搭建 windows下通过pip安装TensorFlow,opencv-pyth ...