李超线段树学习笔记

大概解决形如这样的问题:

  • 加入一条线段。
  • 询问 \(x\) 处哪条线段的 \(y\) 最大/最小。

考虑我们用一颗线段树维护 \(x\in[1,V]\) 的最大。

李超线段树主要通过标记永久化的形式实现,即每个区间维护一个懒标记但是不下传,然后询问的时候把所有懒标记都合并起来。

插入直线

先考虑插入一个直线怎么对已有的懒标记造成影响。

假设查询的是最大。

假设插入一个 \(l_1\),现在的区间是 \([l,r]\),这个区间已有的懒标记是 \(l_2\)

  1. 如果 \(l_1\) 在这个区间内都在 \(l_2\) 的上方:

直接把懒标记替换成 \(l_1\) 即可。

  1. 如果 \(l_1\) 在整个区间都在 \(l_2\) 的下方:

选择 \(l_1\) 一定不比选择 \(l_2\) 优,所以直接返回即可。

  1. 如果 \(l_1\)\(l_2\) 有交点:

我们发现,\([l,mid]\)\([mid+1,r]\) 肯定有一个区间的最值是确定了的,即两个子区间内肯定有一个子区间满足 \(l_1\) 一定比 \(l_2\) 优/劣。

  • 若这个子区间中 \(l_1\)\(l_2\) 劣,直接不递归这个区间即可。
  • 若这个子区间中 \(l_1\)\(l_2\) 优,我们发现有点麻烦,因为子区间也可能有懒标记所以不能直接将这个子区间的懒标记替换为 \(l_1\)

如果直接递归两边复杂度显然不合法,因为无法保证 \([mid+1,r]\)\([l,mid]\) 这两个子区间的懒标记一定有一个满足在 \(l_1\) 的上/下方。

我们考虑将 \([l,r]\) 的懒标记直接替换为 \(l_1\),然后让原来的懒标记 \(l_2\) 递归下去。此时就变成这个子区间新添加的线段劣于懒标记,就和情况 \(1\) 一样了。

每次只递归一侧,复杂度 \(O(\log n)\)

插入线段

即有定义域的限制。

一个简单的办法是直接将其分为 \(O(\log n)\) 个线段树上的区间然后操作。

复杂度 \(O(\log ^2 n)\)

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

如果 \(x_0=x_1\) 直接将其看成一个 \((x_0,\max(y_0,y_1))\) 的点即可。

#include<bits/stdc++.h>
#define sd std::
// #define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define dbg(x) sd cout<<#x<<":"<<x<<" "
#define dg(x) sd cout<<#x<<":"<<x<<"\n"
#define inf 1e10
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=1e5+10,V=39989,P=1e9;
const double eps=1e-6;
int n;
int cmp(double x,double y)
{
	if(x-y>eps) return 1;
	if(y-x>eps) return -1;
	return 0;
}
struct seg
{
	double k,b;
}p[N];
#define ls k<<1
#define rs k<<1|1
int s[V<<2];
double calc(int x,int id)
{
	return 1.0*p[id].k*x+p[id].b;
}
int pd(int i,int j,int p)
{
	int val=cmp(calc(p,i),calc(p,j));
	// if(i==2&&j==3) dbg(p),sd cout<<calc(p,i)<<" "<<calc(p,j)<<"\n";
	return (val==1||(!val&&i<j));
}
void update(int k,int l,int r,int id)
{
	int &tag=s[k],mid=l+r>>1;
	// dbg(l),dg(r);
	// dbg(tag),dg(id);
	if(pd(id,tag,mid)) sd swap(tag,id);
	if(pd(id,tag,l)&&id) update(ls,l,mid,id);
	if(pd(id,tag,r)&&id) update(rs,mid+1,r,id);
}
void add(int k,int l,int r,int x,int y,int id)
{
	if(x<=l&&y>=r)
	{
		update(k,l,r,id);
		return;
	}
	int mid=l+r>>1;
	if(x<=mid) add(ls,l,mid,x,y,id);
	if(y>mid) add(rs,mid+1,r,x,y,id);
}
int ask(int k,int l,int r,int id,int x)
{
	int res=(id?(s[k]&&pd(s[k],id,x))?s[k]:id:s[k]);
	// dbg(l),dg(r);
	// dbg(s[k]),dbg(id),dg(res);
	if(l==r) return res;
	int mid=l+r>>1;
	if(x<=mid) return ask(ls,l,mid,res,x);
	else return ask(rs,mid+1,r,res,x);
}
void solve()
{
	int Q=read(),last=0;
	while(Q--)
	{
		int op=read();
		if(op==0)
		{
			int x=(read()+last-1)%V+1;
			put(last=ask(1,1,V,0,x));
		}
		else
		{
			int x=(read()+last-1)%V+1,y=(read()+last-1)%P+1,i=(read()+last-1)%V+1,j=(read()+last-1)%P+1;
			++n;
			if(x>i) sd swap(x,i),sd swap(y,j);
			if(x==i)
			{
				p[n].k=0;
				p[n].b=1.0*sd max(y,j);
			}
			else
			{
				p[n].k=1.0*(j-y)/(i-x);
				p[n].b=y-p[n].k*x;
			}
			add(1,1,V,x,i,n);
		}
	}
}
signed main()
{
// 	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=1;
	// T=read();
	while(T--) solve();
    return 0;
}
posted @ 2025-09-22 19:53  _E_M_T  阅读(5)  评论(0)    收藏  举报