李超线段树

介绍

李超线段树,是用来解决平面直角坐标系中直线或线段的集合在某一点 x x x处的最大值或最小值问题。

在实现李超线段树的时候,打的标记是不用下传的,也就是标记永久化。在查询时,根节点到对应叶节点上的所有节点的贡献都要计算。

线段树的区间对应的是平面直角坐标系的 x x x轴上的区间,对于每个区间,要维护的是在这个区间上较优的直线或线段。


维护线段

在线段树上的操作如下:

  • 修改操作:加入一条线段
  • 查询操作:查询所有包含 x = x 0 x=x_0 x=x0的线段在 x = x 0 x=x_0 x=x0时能取到的最大值

下面来分类讨论一下每种情况。

  • 如果线段覆盖的这个区间与当前区间并不相交,则直接返回
  • 如果线段覆盖了这个区间的一部分,则递归对应的左右子区间继续处理
  • 如果线段覆盖了整个区间
    • 如果线段在区间两端点处的值都比原来区间大,则用当前线段替换原来的线段并返回
    • 如果线段在区间两端点处的值都比原来区间小,则直接返回
    • 否则,比较当前线段和原来线段在区间中点处的值,如果当前线段值更大,则将原来线段和当前线段交换;然后判断左右端点的值,如果当前线段在一边不占优势,则递归对应左右子区间

修改时要先找到修改分配到 log ⁡ n \log n logn个线段树上的区间,这些区间需要 O ( log ⁡ n ) O(\log n) O(logn)的时间复杂度下传,所以单次修改的时间复杂度为 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n)。同样地,查询也是 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n)

例题

P4097 【模板】李超线段树 / [HEOI2013] Segment

模板题,按上面说的做即可。时间复杂度为 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

code

#include
using namespace std;
const double eps=1e-8;
const long long mod1=39989,mod2=1000000001;
int n,s1,cnt=0,rt,ans=0;
struct node{
	double k,b;
}s[100005];
struct tree{
	int lc,rc,c;
}tr[500005];
bool pd(int od,int wd,int x){
	double y1=s[od].k*x+s[od].b;
	double y2=s[wd].k*x+s[wd].b;
	if(abs(y1-y2)<eps) return od>wd;
	return y1<y2;
}
void insert(int &k,int l,int r,int x,int y,int c){
	if(!k) k=++cnt;
	if(l>=x&&r<=y){
		if(pd(tr[k].c,c,l)&&pd(tr[k].c,c,r)){
			tr[k].c=c;return;
		}
		if(pd(c,tr[k].c,l)&&pd(c,tr[k].c,r)) return;
		int mid=l+r>>1;
		if(pd(tr[k].c,c,mid)) swap(tr[k].c,c);
		if(pd(tr[k].c,c,l)) insert(tr[k].lc,l,mid,x,y,c);
		else insert(tr[k].rc,mid+1,r,x,y,c);
		return;
	}
	if(l>y||r<x) return;
	if(l==r) return;
	int mid=l+r>>1;
	if(x<=mid) insert(tr[k].lc,l,mid,x,y,c);
	if(y>mid) insert(tr[k].rc,mid+1,r,x,y,c);
}
int find(int k,int l,int r,int x){
	if(l==r&&l==x) return tr[k].c;
	int mid=l+r>>1,re=0;
	if(x<=mid) re=find(tr[k].lc,l,mid,x);
	else re=find(tr[k].rc,mid+1,r,x);
	if(pd(re,tr[k].c,x)) re=tr[k].c;
	return re;
}
int main()
{
	scanf("%d",&n);
	for(int i=1,op,v1,v2,w1,w2;i<=n;i++){
		scanf("%d",&op);
		if(op==1){
			scanf("%d%d%d%d",&v1,&w1,&v2,&w2);
			v1=(v1+ans-1)%mod1+1;
			v2=(v2+ans-1)%mod1+1;
			w1=(w1+ans-1)%mod2+1;
			w2=(w2+ans-1)%mod2+1;
			if(v1>v2){
				swap(v1,v2);swap(w1,w2);
			}
			if(v1==v2){
				s[++s1]=(node){0,max(w1,w2)};
			}
			else{
				double k=(w2-w1)*1.0/(v2-v1);
				s[++s1]=(node){k,w1-v1*k};
			}
			
			insert(rt,1,40000,v1,v2,s1);
		}
		else{
			scanf("%d",&v1);
			v1=(v1+ans-1)%mod1+1;
			ans=find(rt,1,40000,v1);
			printf("%d\n",ans);
		}
	}
	return 0;
}

维护直线

与维护线段的方法类似,还要简单一些。不用考虑线段没有覆盖整个区间的情况,再按维护线段的方法实现即可。

修改时直线只分配到 1 1 1个线段树上的区间(就是整个线段树维护的区间),这个区间需要 O ( log ⁡ n ) O(\log n) O(logn)的时间复杂度下传,所以单次修改的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)。同样地,查询也是 O ( log ⁡ n ) O(\log n) O(logn)

例题

P4254 [JSOI2008] Blue Mary 开公司

也是一道模板题,在维护线段的代码的基础上删掉线段没有覆盖整个区间的情况即可。时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

code

#include
using namespace std;
const double eps=1e-8;
int n,s1,cnt=0,rt,ans;
char ch[15];
struct node{
	double k,b;
}s[100005];
struct tree{
	int lc,rc,c;
}tr[500005];
bool pd(int od,int wd,int x){
	double w1=s[od].k+x*s[od].b;
	double w2=s[wd].k+x*s[wd].b;
	if(abs(w1-w2)<eps) return od>wd;
	return w1<w2;
}
void insert(int &k,int l,int r,int c){
	if(!k) k=++cnt;
	if(pd(tr[k].c,c,l)&&pd(tr[k].c,c,r)){
		swap(tr[k].c,c);return;
	}
	if(pd(c,tr[k].c,l)&&pd(c,tr[k].c,r)) return;
	int mid=l+r>>1;
	if(pd(tr[k].c,c,mid)) swap(tr[k].c,c);
	if(pd(tr[k].c,c,l)) insert(tr[k].lc,l,mid,c);
	else insert(tr[k].rc,mid+1,r,c);
}
int find(int k,int l,int r,int x){
	if(l==r&&l==x) return tr[k].c;
	int mid=l+r>>1,re=0;
	if(x<=mid) re=find(tr[k].lc,l,mid,x);
	else re=find(tr[k].rc,mid+1,r,x);
	if(pd(re,tr[k].c,x)) re=tr[k].c;
	return re;
}
int main()
{
	scanf("%d",&n);
	s[0]=(node){0,-1e9};
	for(int i=1,x;i<=n;i++){
		scanf("%s",ch);
		if(ch[0]=='P'){
			double k,b;
			scanf("%lf%lf",&k,&b);
			s[++s1]=(node){k-b,b};
			insert(rt,1,50000,s1);
		}
		else{
			scanf("%d",&x);
			ans=find(rt,1,50000,x);
			ans=s[ans].k+x*s[ans].b;
			if(ans<0) ans=0;
			printf("%d\n",ans/100);
		}
	}
	return 0;
}

你可能感兴趣的:(数据结构,算法,c++,算法,数据结构)