李超线段树

前置知识:普通线段树,一点 小学二年级 初中函数内容。

食用提示:您最好画图。

题目大意

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

题目要求维护一个平面直角坐标系,并对其进行以下操作(强制在线):

  • 在该平面内插入一条起始点为 \((x0,y0)\),终止点为 \((x1,y1)\) 的线段;
  • 给定一个整数 \(k\),查询当前所有线段与直线 \(x=k\) 相交的线段中交点纵坐标最大的线段编号。特殊的,若存在多个交点纵坐标最大的线段,输出编号最小的那一条;若没有相交的线段,则输出 \(0\)

强制在线是因为有恶心人的输入方式。

首先这道题看起来就很数据结构的样子,但是思考了一圈发现普通的线段树之类都无法维护如此诡异狰狞的玩意。

于是在此情景下,李超线段树横空出世。

李超线段树

李超线段树,简单来说就是一种专门处理线段之间关系的线段树。在这个时候,李超线段树不再维护普通的区间信息,而是选择维护在当前区间里的最优线段。

以本题为例,最优线段一定是更加靠上的。

由于题目要求求出与直线 \(x=k\) 交点最靠上的交点所在线段编号,那么很显然绿色线段优于黄色线段,因为绿色线段的交点位置更加靠上。那么在图片所展示的区间内,李超线段树会选择保留绿色线段而非黄色线段。

现在我们成功明确了李超线段树“要维护什么”,就需要考虑“如何去维护”。

插入

当前区间就是从上面这张图片最左端到图片最右端。

肉眼可见,如果据我们之前所说“更靠上的线段更优”,那么很显然当前这个区间里有一部分是原来的线段(黄色)更优,而另一部分则是新加入的线段(蓝色)更优。

紧接着就会发现,在右区间里全部都是新线段(蓝色)更优,在这次插入操作中就会把右区间全面替换为新线段(蓝色)。至于怎么替换这里先不管,甩了。

但左区间情况就些许复杂了,当前区间表示:“我管不了”,遂把烂摊子甩给自己的左儿子也就是左区间,那事儿就不是咱们的了( \(bushi\) )。

总而言之,就是原来线段(黄色)与新插入的线段(蓝色)一定会产生交点,交点就是原线段保留与否的临界点。那么能够进行操作的整区间就对其进行操作,也就是对当前区间的左区间或右区间不进行操作或者用新线段进行替换。剩下的部分就是无法在当前区间处理的,需要甩给自己的儿子。

接下来又很多情况,需要分类讨论(十分非常建议在草稿纸上画一下,会更清晰):

  • 如果这个交点也就是临界点在左区间且右边新线段更靠上(如图所示),那么右区间将完全被新线段覆盖;
  • 若交点依旧在左区间,但是右区间的部分原线段更靠上(相当于是此时把蓝色的当成原线段,黄色的是新线段),那么右区间不需要做任何操作和改动,因为新线段被原线段完全覆盖了;
  • 另外就是交点在右区间,也需要判断左区间两条线段的高低。如果左边新线段更靠上,就把左区间替换为新线段;
  • 同理,若交点在右区间且左区间里原线段更靠上,那就什么都不用干;
  • 接下来是特殊情况:在当前区间内没有交点。首先是两线段没有交点但是新线段更靠上,那么将左右两区间都用新线段替换掉;
  • 如果两线段没有交点但是原线段更靠上,那就什么都不干,把新线段抛了;

至于怎么判断交点两侧的线段高低,完全可以从几个交点的纵坐标推出。

首先计算区间中点,记为 \(mid\)。那么很容易求出两条线段于直线 \(x=mid\) 的交点纵坐标(反正算出来就是了),以及两条线段分别在区间最左边和最右边(\(l\) 处和 \(r\) 处,即为左端点和右端点)处的交点纵坐标。当然交点位置已经在图中标出。

建议您们自己手推一下是如何判断高低关系的 (并不是作者不想写)

好吧还是说一下(诚挚建议您画图):

  • 如果新线段在中点处的交点更靠上,且新线段在右端点处的交点依然靠上。那么很显然新旧两线段交点在左区间,而且在右区间部分新线段比原线段更靠上也就是更优,需要将右区间替换成新线段;
  • 依旧新线段在中点处的交点更靠上但是新线段在左端点处交点更靠上,那么两线段交点在右区间,且左区间将被新线段完全覆盖;
  • 若新线段还是在中点处交点更靠上,但特殊情况下新线段在两端点处的交点都更靠上,那么将左右两区间都替换为新线段;
  • 原线段在中点处更靠上同理,只不过某个区间内原线段更优就不需要对这部分区间进行任何操作;

啊,讲的我好累啊。

覆盖

啊,那当然是使用我们最喜欢的懒标记了。

如果该区间已经有标记了,由于标记难以合并,只能把标记下传.但是子节点也有自己的标记,也可能产生冲突,所以我们要递归下传标记.
引用自 OI - wiki.

没错就是这样。

查询

说白了,跟普通线段树查询没区别(白说了)。

就是每次分左右两个区间然后分到最下层再返回。

复杂度

以前不会,现在不会,将来大概率也不会会。

可以学习 \(OI - Wiki\),写得很好吃。

\(Code\)

#include<bits/stdc++.h>
using namespace std;
const int mod1=39989;
const int mod2=1e9;
const int maxt=40000;
const double eps=1e-9;
using mkp=pair<double,int>; // make pair : 
int cmp(double x,double y){
	if(x-y>eps) return 1;
	if(y-x>eps) return -1;
	return 0;
}
struct line{double k,b;}; // 一次函数斜率和 y 轴截距; 
line p[100005];
int s[200005],cnt;
double calc(int id,int d){return p[id].k*d+p[id].b;} // d 为横坐标,id 为线段编号,计算对应纵坐标值; 
void add(int x0,int y0,int x1,int y1){ // 线段起始坐标和结束坐标;  
	cnt++;
	if(x0==x1) // 特判垂直于 x 轴的情况(没有斜率);  
		p[cnt].k=0,p[cnt].b=max(y0,y1);
	else
		p[cnt].k=1.0*(y1-y0)/(x1-x0),p[cnt].b=y0-p[cnt].k*x0; // 算斜率和 y 轴截距; 
	return;
}
void merge(int id,int cl,int cr,int u){ // 对线段完全覆盖到的区间进行修改;  
	int &v=s[id],mid=(cl+cr)/2;
	int bmid=cmp(calc(u,mid),calc(v,mid)); // 计算中点处原直线更靠上还是当前直线更靠上; 
	if(bmid==1||(bmid==0&&u<v)) swap(u,v);
	int bl=cmp(calc(u,cl),calc(v,cl)),br=cmp(calc(u,cr),calc(v,cr)); // 通过计算左端点处和右端点处的原线段、新线段的上下判断与区间原线段的交点位于左区间还是右区间; 
	if(bl==1||(bl==0&&u<v)) merge(id*2,cl,mid,u);
	if(br==1||(br==0&&u<v)) merge(id*2+1,mid+1,cr,u);
	return;
}
void update(int id,int cl,int cr,int l,int r,int u){ // 定位插入线段完全覆盖到的区间;  
	if(l<=cl&&cr<=r){
		merge(id,cl,cr,u); // 完全包含就修改;  
		return;
	}
	int mid=(cl+cr)/2;
	if(l<=mid) update(id*2,cl,mid,l,r,u); // 依旧分开算;  
	if(r>mid) update(id*2+1,mid+1,cr,l,r,u);
	return;
}
mkp pmax(mkp x,mkp y){
	if(cmp(x.first,y.first)==-1) return y;
	else if(cmp(x.first,y.first)==1) return x;
	else return x.second<y.second?x:y;
}
mkp query(int id,int l,int r,int d){ // 查询;  
	if(r<d||d<l) return {0,0}; // 不包含; 
	int mid=(l+r)/2;
	double res=calc(s[id],d); // 计算当前线段在横坐标为 d 处的对应纵坐标;  
	if(l==r) return {res,s[id]}; // 返回交点;  
	return pmax({res,s[id]},pmax(query(id*2,l,mid,d),query(id*2+1,mid+1,r,d))); // 分开算,那很长了;  
}
int main(){
	int n,lastans=0;
	scanf("%d",&n);
	while(n--){
		int op;
		scanf("%d",&op);
		if(op==1){ // 插入;  
			int x0,y0,x1,y1;
			scanf("%d%d%d%d",&x0,&y0,&x1,&y1);
			x0=(x0+lastans-1+mod1)%mod1+1,x1=(x1+lastans-1+mod1)%mod1+1;
			y0=(y0+lastans-1+mod2)%mod2+1,y1=(y1+lastans-1+mod2)%mod2+1; // 恶心人的强制在线; 
			if(x0>x1) swap(x0,x1),swap(y0,y1);
			add(x0,y0,x1,y1),update(1,1,mod1,x0,x1,cnt);
		}
		else{ // 查询;  
			int x;
			scanf("%d",&x);
			x=(x+lastans-1+mod1)%mod1+1,lastans=query(1,1,mod1,x).second;
			printf("%d\n",lastans);
		}
	}
	return 0;
}

\(-\) ❀ 完结撒花 ❀ \(-\)

posted @ 2026-05-13 23:05  404-NoT_FinD  阅读(44)  评论(0)    收藏  举报