关于线段树

线段树合并

需用结构体存线段树
简单的,板子也好理解

inline int merge(int x, int y, int l, int r){ //将 y 树合并到 x 树中
	if(!x || !y){
		return x | y; //返回节点
	}
	
	if(l == r){
		tree[x].v += tree[y].v; //叶子节点合并
		return x;
	}
	
	tree[x].l = merge(tree[x].l, tree[y].l, l, mid);
	tree[x].r = merge(tree[x].r, tree[y].r, mid + 1, r); //重新分配节点
	
	pushup(x); //x 树上传更新
	return x;
}

动态开点

当到了未建立过的新点时再建立点,一般用结构体来存储线段树。

大致代码:

#define lx tree[x].l
#define rx tree[x].r
#define mid ((l + r) >> 1)

int cnt;
struct node{
	int l, r;
	int v;
}tree[N << 5];

inline void pushup(int x){
	tree[x].v = tree[lx].v + tree[rx].v;
}

inline int update(int x, int l, int r, int k, int s){ //此处以单点修改展示
	if(!x) x = ++ cnt;
	
	if(l == r){
	tree[x].v = s;
		return x;
	}
	
	if(k <= mid) tree[x].l = update(lx, l, mid, k);
	if(mid < k) tree[x].r = update(rx, mid + 1, r, k);
	
	pushup(x);
	return x;
}

关于线段树开的空间……
我的评价是:能开大点就开大点,保险

可持久化

常在多颗线段树差异不大但都需访问时使用
即在动态开点上加个记录时间的标记(也可以是其他标记),以达到节省空间开多颗线段树
\(rt_i\) 表示第 \(i\) 颗线段树的根节点
一般来说至少要开 \(N << 5\) 的空间

注意点:

  1. 一定要先建 \(rt[0]\) 的初始树,即使它可能是颗空树
  2. 开新点的过程大多为直接复制原节点再修改
  3. 相比正常线段树需多用一 \(vis\) 数组来表示该点是否开过
  4. 一定要有回传标号的操作
  5. 可持久化不能使用线段树合并,会有重复合并的错误(亲身经历)
int rt[N], cnt;
struct node{
	int l, r;
	int v;
	bool vis; //vis 表示当点是否被开过
}tree[N << 5];

inline void pushup(int x){
	tree[x].v = tree[lx].v + tree[rx].v;
}

inline int build(int x, int l, int r){
	x = ++ cnt;
	
	if(l == r){
		return x; //回传标号
	}
	
	tree[x].l = build(lx, l, mid);
	tree[x].r = build(rx, mid + 1, r);
	
	return x; //同上
}

inline int add(int x){
	tree[++ cnt] = tree[x];
	tree[cnt].vis = 1;
	tree[tree[cnt].l].vis = 0;
	tree[tree[cnt].r].vis = 0;
	return cnt;
}

inline int update(int x, int l, int r, int k, int s){
	if(!tree[x].vis){
		x = add(x);
	}
	
	if(l == r){
		tree[x].v = s;
		tree[x].vis = 1;
		return x;
	}
	
	if(k <= mid) tree[x].l = update(lx, l, mid, k, s);
	if(mid < k) tree[x].r = update(rx, mid + 1, r, k, s);
	
	pushup(x);
	return x;
}

int main(){
	rt[0] = build(1, 1, n);
	for(int i = 1; i <= n; ++ i){
		rt[i] = add(rt[i - 1]); //第 i 颗树继承(复制)第 i - 1 颗树
		rt[i] = update(rt[i], 1, n, i, 1);
	}
}

李超线段树

luogu - 李超线段树
zhihu - 李超线段树

用于处理坐标轴中线段……嘶……我描述不出来,思路什么的就去看上面两篇文章吧,这里只给出本人常用的模板。

#define lx (x << 1)
#define rx (x << 1 | 1)
#define mid ((l + r) >> 1)

int k[N], b[N];

inline int find(int p, int x){
//计算第 p 条线段上横坐标为 x 的值  
	return k[p] * x + b[p];
}

int tree[N << 2];

//此代码以求最大值为展示,注释掉部分为求最小值写法  
inline void update(int x, int l, int r, int p){
//x 表示树上标号,区间 [l, r] 表示横坐标 
	if(l == r){
	//当递归到了单点  
		if(find(tree[x], l) < find(p, l)){
		//比较当前横坐标上的高度  
			tree[x] = p;
		}
/*		if(find(tree[x], l) > find(p, l)){
			tree[x] = p;
		}*/
		return ;
	}
	
	if(!tree[x]){
	//这段区间上没有线段  
		tree[x] = p;
		return ;
	}
	
	if(k[tree[x]] == k[p]){
	//两条线段斜率相等  
		if(b[tree[x]] < b[p]){
		//比较 b ,即比较相对高度  
			tree[x] = p;
		}
/*		if(b[tree[x]] > b[p]){
			tree[x] = p;
		}*/
		return ;
	}
	
	//以下画图会更好理解 
	int lst = find(tree[x], mid);
	int now = find(p, mid);
	//比较中间点相对位置  
	
	if(k[tree[x]] < k[p]){
	//原线段斜率小于加入线段斜率 
	//画图理解罢  
		if(lst <= now){
			update(lx, l, mid, tree[x]);
			tree[x] = p;
		}
		else{
			update(rx, mid + 1, r, p);
		}
/*		if(lst < now){
			update(lx, l, mid, p);
		}
		else{
			update(rx, mid + 1, r, tree[x]);
			tree[x] = p;
		}*/
	}
	
	else{
		if(lst <= now){
			update(rx, mid + 1, r, tree[x]);
			tree[x] = p;
		}
		else{
			update(lx, l, mid, p);
		}
/*		if(lst < now){
			update(rx, mid + 1, r, p);
		}
		else{
			update(lx, l, mid, tree[x]);
			tree[x] = p;
		}*/
	}
	
	return ;
}

//同上 
inline int query(int x, int l, int r, int p){
//查询横坐标为 p 的点的最大 / 最小值  
	if(l == r){
		return find(tree[x], p);
	}
	
	int sum = find(tree[x], p);
	//当前区间最优  
	
	if(p <= mid) return max(sum, query(lx, l, mid, p));
	if(mid < p) return max(sum, query(rx, mid + 1, r, p));
/*	if(p <= mid) return min(sum, query(lx, l, mid, p));
	if(mid < p) return min(sum, query(rx, mid + 1, r, p));*/
}
这种李超树写法貌似有点长,但常数是优的

实质上,李超线段树中存的只是当前区间最优解,故我们修改和查询时都得遍历到叶子结点为止。
修改复杂度 \(O(log^{2}n)\)
查询复杂度 \(O(log n)\)

在求解斜率优化dp的题目中都可用李超线段树解决,但是要注意单调性和离散化的问题,可以多做做题目参考参考代码

若转移方程式为 dp[i] = k[j] * x[i] + b[j]
则 find 函数应为

inline int find(int p, int i){
	return k[p] * x[i] + b[p];
}

这也是一类似于离散化的操作

posted @ 2023-10-05 19:53  Biuld  阅读(8)  评论(0编辑  收藏  举报