李超线段树 复习

作用

李超树是拿来维护线段或者直线,支持动态插入线段/直线,和询问在某一单点的最大值线段。

其实就是维护了一个动态凸包。

实际上就是维护了一个“最优势线段”和“标记永久化”。

常常用来维护斜率优化 dp 。

实现

插入直线

首先是插入直线,查询单点的实现:

要记住的地方:

(1.) (Modify) 递归到 (l=r) 的时候可以判断当前单点情况然后更新,直接返回。

(2.) (Modify) 递归到 (tag[x]) 没有值的时候直接更新然后返回。

(3.) (Modify) 更新到当前区间,先比较,然后更新当前的最优势线段,然后递归即可。

(4.) (Modify)(0) 号线段需要重置。

(5.) (Query) 注意要询问沿途经过的所有标记对于当前点的值,因为是标记永久化。

(6.) 如果询问单点的值域太大记得要动态开点。

代码:(维护最小值)

int n,m,top;
int tag[N<<2],k[N],b[N];
#define calc(i,x) (k[i]*x+b[i])
void Modify(int x,int l,int r,int d){
	if(l==r){if(calc(d,l)<calc(tag[x],l)) tag[x]=d;return ;}
	if(!tag[x]){tag[x]=d;return ;}
	int mid=l+r>>1;
	int Y1=calc(tag[x],mid),Y2=calc(d,mid);
	if(k[tag[x]]<k[d]){
		if(Y1<=Y2) Modify(x<<1,l,mid,d);
		else Modify(x<<1|1,mid+1,r,tag[x]),tag[x]=d;
	}
	else if(k[tag[x]]>k[d]){
		if(Y1<=Y2) Modify(x<<1|1,mid+1,r,d);
		else Modify(x<<1,l,mid,tag[x]),tag[x]=d;
	}
	else if(b[tag[x]]<b[d]) tag[x]=d;
	return ;
}
int Query(int x,int l,int r,int d){
	if(l==r) return calc(tag[x],d);
	int mid=l+r>>1;int res=calc(tag[x],d);
	if(d<=mid) res=min(res,Query(x<<1,l,mid,d));
	else res=min(res,Query(x<<1|1,mid+1,r,d));
	return res;
}

插入线段

然后是插入线段的操作,具体来说就是要重载一下 (Modify) 函数,只有当我们递归到完全覆盖才进行普通修改。

int n,m,tag[N<<2],tot;
double k[N],b[N];
#define calc(i,x) (k[i]*x+b[i])
void Modify(int x,int l,int r,int d){
	if(l==r){if(calc(d,l)>=calc(tag[x],l)) tag[x]=d;return ;}
	if(!tag[x]){tag[x]=d;return ;}
	int mid=l+r>>1;
	double Y1=calc(tag[x],mid),Y2=calc(d,mid);
	if(k[tag[x]]<k[d]){
		if(Y1<=Y2) Modify(x<<1,l,mid,tag[x]),tag[x]=d;
		else Modify(x<<1|1,mid+1,r,d);
	}
	else if(k[tag[x]]>k[d]){
		if(Y1<=Y2) Modify(x<<1|1,mid+1,r,tag[x]),tag[x]=d;
		else Modify(x<<1,l,mid,d);
	}
	else if(b[tag[x]]<=b[d]) tag[x]=d;
	return ;
}
void Modify(int x,int l,int r,int ql,int qr,int d){
	if(ql<=l&&r<=qr){Modify(x,l,r,d);return ;}
	int mid=l+r>>1;
	if(ql<=mid) Modify(x<<1,l,mid,ql,qr,d);
	if(qr>mid) Modify(x<<1|1,mid+1,r,ql,qr,d);
	return ;
}
int Query(int x,int l,int r,int d){
	if(l==r) return tag[x];
	int mid=l+r>>1;double res=calc(tag[x],d);
	if(d<=mid){
		int ls=Query(x<<1,l,mid,d);
		if(res<calc(ls,d)) return ls;
		else if(calc(ls,d)<res) return tag[x];
		else return tag[x]>ls?tag[x]:ls;
	}
	else{
		int rs=Query(x<<1|1,mid+1,r,d);
		if(res<calc(rs,d)) return rs;
		else if(calc(rs,d)<res) return tag[x];
		else return tag[x]>rs?tag[x]:rs;
	}
	return tag[x];
}

李超线段树合并&动态开点李超线段树

和普通线段树合并没有太大区别,主要就是 (Merge) 函数。

代码:

ll k[N],b[N];
int tag[N<<2],ls[N<<2],rs[N<<2],tot;
#define calc(i,x) (k[i]*(x-Base)+b[i])
void Modify(int &x,int l,int r,int d){
	if(!x){
		x=++cur,tag[x]=d;
		return ;
	}
	if(l==r){if(calc(d,l)<calc(tag[x],l)) tag[x]=d;return ;}
	if(!tag[x]){tag[x]=d;return ;}
	int mid=l+r>>1;
	ll Y1=calc(tag[x],mid),Y2=calc(d,mid);
	if(k[tag[x]]<k[d]){
		if(Y1<=Y2) Modify(ls[x],l,mid,d);
		else Modify(rs[x],mid+1,r,tag[x]),tag[x]=d;
	}
	else if(k[tag[x]]>k[d]){
		if(Y1<=Y2) Modify(rs[x],mid+1,r,d);
		else Modify(ls[x],l,mid,tag[x]),tag[x]=d;
	}
	else if(b[tag[x]]>b[d]) tag[x]=d;
	return ;
}
ll Query(int x,int l,int r,int d){
	if(!x) return INF;
	if(l==r) return calc(tag[x],d);
	int mid=l+r>>1;ll res=calc(tag[x],d);
	if(d<=mid) res=min(res,Query(ls[x],l,mid,d));
	else res=min(res,Query(rs[x],mid+1,r,d));
	return res;
}
int Merge(int x,int y,int l,int r){
	if(!x||!y) return x|y;
	if(l==r) return calc(tag[x],l)>calc(tag[y],l)?y:x;
	int mid=l+r>>1;
	ls[x]=Merge(ls[x],ls[y],l,mid);
	rs[x]=Merge(rs[x],rs[y],mid+1,r);
	Modify(x,l,r,tag[y]);
	return x;
}

例题

P4254 [JSOI2008]Blue Mary开公司

P4097 [HEOI2013]Segment

P3195 [HNOI2008]玩具装箱

P4069 [SDOI2016]游戏

P6047 丝之割

CF1303G Sum of Prefix Sums

CF932F Escape Through Leaf

P2900 [USACO08MAR]Land Acquisition G

P3628 [APIO2010]特别行动队

原文地址:https://www.cnblogs.com/Akmaey/p/14677431.html