2.6-2.7&2.8测试

Link Cut Tree(动态树)

概念讲解

LCT维护的对象其实是一个森林。
在实链剖分的基础下,LCT支持更多的操作,即树剖升级版,但在实际做题中因为树剖的常数小且相对容易调试,所以能用树剖就尽量用树剖。

  1. 查询、修改链上的信息(最值,总和,异或和等)
  2. 随意指定原树的根(即换根)
  3. 动态连边、删边
  4. 合并两棵树、分离一棵树(跟上面不是一毛一样吗)
  5. 动态维护连通性
  6. 更多操作

操作

access(x)

作用: 把从根到 x 的所有点放在一条实链里,使根到 x 成为一条实路径,并且在同一棵 Splay 里

代码实现:(循环处理)

  1. 转到根
  2. 换儿子
  3. 更新信息
  4. 操作点切换为轻边所指的父亲,然后进行操作1
void access(int x){
	for(int y=0;x;y=x,x=fa[x]){
		splay(x);
		ch[x][1]=y;
		updat(x);
	}
}

makeroot(x)

作用:使 x 点成为其所在树的根

void makeroot(int x){
	access(x);
	splay(x);
	reverse(x);
}

split(x,y)

作用:提取出 x, y 间的路径,方便做区间操作

void split(int x,int y){
	makeroot(x);
	access(y);
	splay(y);
}

link(x,y)

作用:在 x, y 两点间连一条边

void link(int x,int y){
	makeroot(x);
	fa[x]=y;
}

cut(x,y)

作用:把 x, y 两点间边删掉

void cut(int x,int y){
	split(x,y);
	if(ch[y][0]!=x||ch[x][1])return ;
	fa[x]=ch[y][0]=0;
	updat(y);
}

findroot(x)

作用:找到 x 所在树的根节点编号

int findroot(int x){
	access(x);
	splay(x);
	while(ch[x][0])pushdown(x),x=ch[x][0];
	splay(x);
	return x;
}

模板:洛谷P3690

模板代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
struct node {
	int fa,ch[2],sum,val,lazy;
} t[N];    //lazy用来标记reverse()的左右翻转
#define lc t[x].ch[0]    //左儿子
#define rc t[x].ch[1]    //右儿子
bool isRoot(int x) {     //判断是否是splay根节点
	int g=t[x].fa;
	return t[g].ch[0]!=x && t[g].ch[1]!=x;//若为根,则父结点不应该有这个儿子
}
void pushup(int x) {     //本题的求路径异或和。上传信息
	t[x].sum=t[x].val^t[lc].sum^t[rc].sum;
}
void reverse(int x) {
	if(!x)return;
	swap(lc,rc);         //翻转x的左右儿子
	t[x].lazy^=1;        //懒惰标记,先不翻转儿子的后代,后面再翻转
}
void pushdown(int x) {   //递归翻转x的儿子的后代,并释放懒标记。
	if(t[x].lazy) {
		reverse(lc);
		reverse(rc);
		t[x].lazy=0;
	}
}
void push(int x) {
	if(!isRoot(x))  push(t[x].fa);  //从根到x全部pushdown
	pushdown(x);
}
void rotate(int x) {
	int y=t[x].fa;
	int z=t[y].fa;
	int k=t[y].ch[1]==x;
	if(!isRoot(y)) t[z].ch[t[z].ch[1]==y]=x;//这里要稍微注意 
	t[x].fa=z;
	t[y].ch[k]=t[x].ch[k^1];
	if(t[x].ch[k^1])t[t[x].ch[k^1]].fa=y;
	t[y].fa=x;
	t[x].ch[k^1]=y;
	pushup(y);
}
void splay(int x) {    //提根:把x旋转为它所在的Splay树的根
	int y,z;
	push(x);           //先pushdown处理x的所有子孙的lazy标记
	while(!isRoot(x)) {
		y=t[x].fa,z=t[y].fa;
		if(!isRoot(y))
			(t[z].ch[0]==y)^(t[y].ch[0]==x)?rotate(x):rotate(y);
		rotate(x);
	}
	pushup(x);
}
void access(int x) {   //在原树上建一条实链,起点是根,终点是x
	for(int child=0; x; child=x, x=t[x].fa) { //从x往上走,沿着虚边走到根
		splay(x);
		rc = child;     //右孩子是child,建立了一条实边
		pushup(x);//路径信息处理 
		if(child) t[child].fa=x;
	}
}
void makeroot(int x) { //把x在原树上旋转到根的位置
	//使节点x成为原树树根,该操作等价于将“根节点到节点x”路径上所有树边的方向取反 
	access(x);
	splay(x);
	reverse(x);//在原树发生变化 
}
void split(int x,int y) {  //把原树上以x为起点、y为终点的路径,生成一条实链
	makeroot(x);
	access(y);
	splay(y);
}
void link(int x,int y) {  //在结点x和y之间连接一条边
	makeroot(x);
	t[x].fa=y;
}
void cut(int x,int y) { //将x,y的边切断
	split(x,y);
	if(t[y].ch[0]!=x||rc)  return;
	t[x].fa=t[y].ch[0]=0;
	pushup(y);
}
int findroot(int x) { //查找x在原树上的根,也可以直接判断两点的连通性。 
	access(x);
	splay(x);
	while(lc)  pushdown(x),x=lc;    //找Splay树最左端的结点
	return x;
}
int main() {
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; ++i) {
		scanf("%d",&t[i].val);
		t[i].sum = t[i].val;
	}
	while(m--) {
		int opt,a,b;
		scanf("%d%d%d",&opt,&a,&b);
		switch(opt) {
			case 0:
				split(a,b);
				printf("%d\n",t[b].sum);
				break;
			case 1:
				if(findroot(a) != findroot(b)) link(a,b);
				break;
			case 2:
				cut(a,b);
				break;
			case 3:
				splay(a);
				t[a].val=b;
				break;
		}
	}
	return 0;
}

讲的比较好的博客

我的题单

左偏树

2.8测试

P2569 [SCOI2010]股票交易

一道DP题
f[i][j]表示第i天拥有j张股票可以赚到的最多钱数。然后分情况讨论。

  1. 凭空买
f[i][j]=-ap[i]*j;
  1. 不买也不卖
f[i][j]=max(f[i][j],f[i-1][j]);
  1. 在之前的基础上买
int l=1,r=0;
		for(int j=0;j<=m;j++){
			while(l<=r&&q[l]<j-as[i])l++;
			while(l<=r&&f[i-w-1][q[r]]+q[r]*ap[i]<=f[i-w-1][j]+j*ap[i])r--;
			q[++r]=j;
			f[i][j]=max(f[i][j],f[i-w-1][q[l]]+q[l]*ap[i]-j*ap[i]);
		}
  1. 在之前的基础上卖
l=1,r=0;
		for(int j=m;j>=0;j--){
			while(l<=r&&q[l]>j+bs[i])l++;
			while(l<=r&&f[i-w-1][q[r]]+q[r]*bp[i]<=f[i-w-1][j]+j*bp[i])r--;
			q[++r]=j;
			f[i][j]=max(f[i][j],f[i-w-1][q[l]]+q[l]*bp[i]-j*bp[i]);
		}

P4331 [BalticOI 2004]Sequence 数字序列(左偏树)

本题有两个性质:

  1. 如果a是一个不下降序列,那么b[i]==a[i]时取得最优解。
  2. 如果a是一个严格递减序列,则取a序列的中位数x,b[i]=x,是最优解。

而整个a序列正是由一些单调区间组成的。所以一但加入的数小于前一个数,那么把当前的数与前一个数合并,维护合并后的中位数。

维护树上染色连通块
一个颜色,建一棵LCT树来维护。

比较坑的一点是,如果改变的颜色是原来的颜色,也算Success

lct模板,但思维上有难度,考试时我只记录了从左到右的值,没有记录从右到左的值,只拿了10分

因为是求链上的信息所以容易想到树链剖分或LCT。

以下是lct需要维护的变量以及区间信息合并:

  1. maxx 和 minn 维护最大值、最小值。
  2. lmax 维护从左到右走获得的最大值。
  3. rmax 维护从右到左走获得的最大值。

upadt的重要片段:

void updat(int x){
	mx[x]=max(mx[lc],max(mx[rc],val[x]));
	mi[x]=min(min(mi[lc],mi[rc]),val[x]);
	lm[x]=max(max(lm[lc],lm[rc]),max(mx[lc],val[x])-min(mi[rc],val[x]));
	rm[x]=max(max(rm[lc],rm[rc]),max(mx[rc],val[x])-min(mi[lc],val[x]));
}

ps:维护从右到左的最大利润的原因是交换左右儿子时最大利润也要左右要翻转

void reverse(int x){
	if(!x)return ;
	swap(lm[x],rm[x]);
	swap(ch[x][0],ch[x][1]);
	laz[x]^=1;
}
posted @ 2023-02-07 10:25  两只风小鱼  阅读(36)  评论(0)    收藏  举报