李超线段树学习笔记

李超线段树学习笔记

李超线段树,是一种维护一次函数最值的数据结构,其结构类似于线段树,由大神李超发明,故称之为李超线段树。
前置知识:

1.线段树
2.求两直线交点坐标
代码在这里:

#define N 100500
struct node{
	int l,r,id;
}t[N<<2];
#define lc x<<1
#define db double
#define rc x<<1|1
db fabs(db a){
	return (a<0)?-a:a;
}
void build(int x,int l,int r){
	t[x].l=l,t[x].r=r;
	if(l==r)return ;
	int mid=l+r>>1;
	build(lc,l,mid);build(rc,mid+1,r); 
}
int cnt,x1[N],y1[N],x2[N],y2[N];
struct line{
	db k,b;
}in[N];
void add(int id){
	if(x1[id]==x2[id]){
		in[id].k=0;in[id].b=max(y1[id],y2[id]);
		return ;
	}
	in[id].k=1.0*(y1[id]-y2[id])/(x1[id]-x2[id]);
	in[id].b=(db)y1[id]-in[id].k*x1[id];
}

李超线段树资瓷2个基本操作:

  1. 插入一条线段(x0,y0,x1,y1)
  2. 给定整数k,支持查询给定线段中x=k时的最大纵坐标及其所属线段。

其核心思想是,对于每个区间,它所存储的是优势最大的线段(在最高折线中有最长的一段)。
划分区间的方案类似于线段树。
类比线段树,李超线段树的查询也是对于区间进行的,一次查询就枚举所有的李超线段树里包含k的区间的最优势线段,对所有的最优势线段取max即可。

#define pr pair<db,int> 
#define mk make_pair
pr fmax(pr a,pr b){
	if(fabs(a.first-b.first)<1e-9){
		if(a.second>b.second)return b;
		return a;
	}
	return max(a,b); 
}
pr find(int x,int k){
	pr ans;
	int y=t[x].id;
	if(t[x].l==t[x].r)return mk(get(y,k),y);
	if(y)ans=mk(get(y,k),y);
	else ans=mk(0,0);
	if(k<=t[lc].r)ans=fmax(ans,find(lc,k));
	else ans=fmax(ans,find(rc,k));
	return ans;
}

问题在于插入:
下面我们来分类讨论插入线段x

  1. 区间里原本没有线段->直接更新
  2. 区间原有线段rt,对其进行比较:
    我们取中点mid的值来进行对比,如下图:
    image
    就会神秘的发现:
    mid处取值更大的线段就是这个区间的最大优势线段
    此时我们将最大优势线段更新,如果x优于rt,就执行swap(x,rt)(此时rt就成了新插入的线段)但,这就完了吗?

不一定,新插入的线段即使不是当前区间的最大优势线段,也有可能更新儿子区间。
仍然是之前那张图。容易发现,若yx,r>yrt,r,则x可能更新右儿子。同理,若yx,l>yrt,l则有可能更新左儿子,这些都进行递归处理即可。
复杂度的话,由于yx,mid<yrt,mid,所以最多递归一个儿子(瞎胡的,欢迎斧正),所以应该是O(nlog2n)的样子。
所以代码实际肥肠简单。

void updata(int x,int k){
	int mid=t[x].l+t[x].r>>1;
	if(get(t[x].id,mid)<=get(k,mid))swap(k,t[x].id);
	if(t[x].l==t[x].r)return ;
	if(get(t[x].id,t[x].l)<get(k,t[x].l)||(
	get(t[x].id,t[x].l)==get(k,t[x].l)&&t[x].id>k))updata(lc,k);
	if(get(t[x].id,t[x].r)<get(k,t[x].r)||(
	get(t[x].id,t[x].r)==get(k,t[x].r)&&t[x].id>k))updata(rc,k);//这里是因为题目要求编号最小
}
void change(int x,int l,int r,int k){
	if(l<=t[x].l&&t[x].r<=r){
		updata(x,k);
		return ;
	}
	int mid=t[x].l+t[x].r>>1;
	if(l<=mid)change(lc,l,r,k);
	if(mid<r)change(rc,l,r,k);
}
 

同理,如果是求最小的话反过来就是了。
以模板题HEOI2016线段树为例:

#include<iostream>
#include<cstdio>
using namespace std;
#define N 100500
struct node{
	int l,r,id;
}t[N<<2];
#define lc x<<1
#define db double
#define rc x<<1|1
db fabs(db a){
	return (a<0)?-a:a;
}
void build(int x,int l,int r){
	t[x].l=l,t[x].r=r;
	if(l==r)return ;
	int mid=l+r>>1;
	build(lc,l,mid);build(rc,mid+1,r); 
}
int cnt,x1[N],y1[N],x2[N],y2[N];
struct line{
	db k,b;
}in[N];
void add(int id){
	if(x1[id]==x2[id]){
		in[id].k=0;in[id].b=max(y1[id],y2[id]);
		return ;
	}
	in[id].k=1.0*(y1[id]-y2[id])/(x1[id]-x2[id]);
	in[id].b=(db)y1[id]-in[id].k*x1[id];
}
db get(int id,int x){
	return (db)in[id].k*x+in[id].b;
}
#define pr pair<db,int> 
#define mk make_pair
pr fmax(pr a,pr b){
	if(fabs(a.first-b.first)<1e-9){
		if(a.second>b.second)return b;
		return a;
	}
	return max(a,b); 
}
pr find(int x,int k){
	pr ans;
	int y=t[x].id;//cout<<x<<" "<<t[x].l<<" "<<t[x].r<<" "<<y<<endl;
	if(t[x].l==t[x].r)return mk(get(y,k),y);
	if(y)ans=mk(get(y,k),y);
	else ans=mk(0,0);
	if(k<=t[lc].r)ans=fmax(ans,find(lc,k));
	else ans=fmax(ans,find(rc,k));
	return ans;
}
void updata(int x,int k){
	int mid=t[x].l+t[x].r>>1;
	if(get(t[x].id,mid)<=get(k,mid))swap(k,t[x].id);
	if(t[x].l==t[x].r)return ;
	if(get(t[x].id,t[x].l)<get(k,t[x].l)||(
	get(t[x].id,t[x].l)==get(k,t[x].l)&&t[x].id>k))updata(lc,k);
	if(get(t[x].id,t[x].r)<get(k,t[x].r)||(
	get(t[x].id,t[x].r)==get(k,t[x].r)&&t[x].id>k))updata(rc,k);
}
void change(int x,int l,int r,int k){
	if(l<=t[x].l&&t[x].r<=r){
		updata(x,k);
		return ;
	}
	int mid=t[x].l+t[x].r>>1;
	if(l<=mid)change(lc,l,r,k);
	if(mid<r)change(rc,l,r,k);
}
int lst,n;
int main(){
//	freopen("data.in","r",stdin);
//	freopen("data.out","w",stdout);
	ios::sync_with_stdio(false);
	cin>>n;
	build(1,1,40000);
	for(int i=1;i<=n;i++){
		int op;cin>>op;
		if(!op){
			int k;cin>>k;k=(k+lst+39988)%39989+1;
			lst=find(1,k).second;
			cout<<lst<<"\n";
		}
		else {
			swap(++cnt,i);
			cin>>x1[i]>>y1[i]>>x2[i]>>y2[i];
			x1[i]=(x1[i]+lst+39988)%39989+1;
			x2[i]=(x2[i]+lst+39988)%39989+1;
			y1[i]=(y1[i]+lst+999999999)%1000000000+1;
			y2[i]=(y2[i]+lst+999999999)%1000000000+1;
			if(x1[i]>x2[i]){
				swap(x1[i],x2[i]);
				swap(y1[i],y2[i]);
			}
			add(i);
		//	cout<<in[i].k<<" "<<in[i].b<<endl;
			change(1,x1[i],x2[i],i);
			swap(i,cnt);
		}
	} 
}

而因为李超线段树的特性,也被用于斜率优化,主要是将斜率式转化为点斜式方程y=kx+b,然后就每次需要查询y=0时的最小/大值,就是所求的最小/大截距。这样就不需要考虑二分了。尤其是方便分不清楚大于小于的同学,付出一个O(logn)的代价,换回了绝大部分分数(常熟小可以到一百)。虽然李超不见得多好写.

posted @   spdarkle  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示