李超线段树
要求在平面直角坐标系下维护两个操作:
在平面上加入一条线段。记第 \(i\) 条被插入的线段的标号为 \(i\)。
给定一个数 \(k\),询问与直线 \(x = k\) 相交的线段中,交点纵坐标最大的线段的编号。
特别地,如果线段是竖直的,则取最上面的点。线段若不存在,则输出 \(0\)。
对于 \(100\%\) 的数据,保证 \(1 \leq n \leq 10^5\),\(1 \leq k, x_0, x_1 \leq 39989\),\(1 \leq y_0, y_1 \leq 10^9\)。强制在线。
李超线段树
插入线段即插入定义域有限制的一次函数。竖直线段即定义域为 \([x,x]\) 的一次函数 \(y=b\)。
因此可以考虑线段树维护 \(x\) 轴上的区间操作,维护区间最高一次函数。(李超树不能维护线段删除。)
传统线段树无法很好地维护,因此有了李超线段树。
一次函数存储
可以结构体配合重载运算符。
struct node{
int x,y;
};
struct line{
double k,b;
line(){
}
line(node a,node b){
if(a.x==b.x){
k=0;
line::b=max(a.y,b.y);
}else{
k=1.0*(a.y-b.y)/(a.x-b.x);
line::b=a.y-k*a.x;
}
}
double operator ()(int x){
return k*x+b;
}
}a[N+1];
建树
其实没必要,不存储区间端点则不需要建树。一个点维护的信息仅有 \(\textit{id}\),而初始时均为 \(0\)。
struct node{
int l,r;
int id;//线段编号
}t[X<<2|1];
void build(int p,int l,int r){
t[p]={l,r};
if(l==r){
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
精度问题
double
具有浮点误差,需要避免。设 eps
为最小精度,本文取 \(10^{-9}\)。
重定义小于(leq
)、大于(geq
)和等于(eq
)。
bool leq(double a,double b){
return a-b<-eps;
}
bool geq(double a,double b){
return a-b>eps;
}
bool eq(double a,double b){
return abs(a-b)<=eps;
}
插入线段
其实类似于扫描线的线段树,节点标记并不会下传。
先考虑找出所有在定义域内的线段树节点区间 \([l,r]\)。考虑对于每一个线段树节点,维护节点区间内的最高线段编号 \(\textit{id}\),且 \([l,r]\) 是定义域内的极大区间,其父区间并不被定义域包含。(\(\textit{id}\) 并不会下传)
设插入线段编号为 \(x\),和 \(x\) 定义域内区间节点 \([l,r]\)。所有的 \([l,r]\) 都可以在线段树上 \(\mathcal O(\log n)\) 找出来。
找到区间后,考虑更新 \(\textit{id}\)。
-
如果 \([l,r]\) 内 \(\textit{id}=0\),即不存在,自然最好,可以直接 \(\textit{id}\leftarrow x\)。
-
否则 \(\textit{id}\) 已经存在,则需要讨论。
记 \(\textit{mid}=\left\lfloor\dfrac{l+r}2\right\rfloor\)。
取 \(\textit{id},x\) 在区间中点 \(x=\textit{mid}\) 处的取值 \(y_{\textit{id}},y_x\)。
-
若 \(y_x>y_{\textit{id}}\),则说明 \(x\) 一定更优,因此可以 \(x\longleftrightarrow\textit{id}\),即交换 \(x,\textit{id}\) 的取值。
-
有:
- 若在 \(l\) 处有 \(\textit{y}_x>\textit{id}\),说明 \(x,\textit{id}\) 在 \([l,\textit{mid}]\) 中相交,递归判断左子区间。
- 若在 \(r\) 处有 \(y_x>\textit{id}\),说明 \(x,\textit{id}\) 在 \([\textit{mid}+1,r]\) 中相交,递归判断右子区间。
这二者显然至多一个成立,因此复杂度 \(\mathcal O(\log n)\)。
-
时间复杂度:\(\mathcal O\left(\log^2n\right)\)。
bool check(int u,int v,int x){
double yU=a[u](x),yV=a[v](x);
return geq(yU,yV)||eq(yU,yV)&&u<v;
}
void down(int p,int x){
int &id=t[p].id;
if(t[p].l==t[p].r){
if(check(x,id,t[p].l)){
id=x;
}
return;
}
if(check(x,id,t[p<<1].r)){
swap(x,id);
}
if(check(x,id,t[p<<1].l)){
down(p<<1,x);
}
if(check(x,id,t[p<<1|1].r)){
down(p<<1|1,x);
}
}
void update(int p,int l,int r,int x){
if(l<=t[p].l&&t[p].r<=r){
down(p,x);
return;
}
if(l<=t[p<<1].r){
update(p<<1,l,r,x);
}
if(t[p<<1|1].l<=r){
update(p<<1|1,l,r,x);
}
}
查询线段
单点查询,查询路径上所有的 \(\textit{id}\) 中最优的一个即可。
时间复杂度:\(\mathcal O(\log n)\)。
void chkmax(int &u,int v,int x){
double yU=a[u](x),yV=a[v](x);
if(yV>yU){
u=v;
}else if(eq(yU,yV)&&v<u){
u=v;
}
}
int query(int p,int x){
int ans=t[p].id;
if(t[p].l==t[p].r){
return ans;
}
if(x<=t[p<<1].r){
chkmax(ans,query(p<<1,x),x);
}else{
chkmax(ans,query(p<<1|1,x),x);
}
return ans;
}
例题 AC 代码
注意强制在线的写法,不要模数取错。
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=1e5,X=39989,Y=1e9;
constexpr const double eps=1e-9;
struct node{
int x,y;
};
struct line{
double k,b;
line(){
}
line(node a,node b){
if(a.x==b.x){
k=0;
line::b=max(a.y,b.y);
}else{
k=1.0*(a.y-b.y)/(a.x-b.x);
line::b=a.y-k*a.x;
}
}
double operator ()(int x){
return k*x+b;
}
}a[N+1];
struct LiChaoSegTree{
struct node{
int l,r;
int id;//线段编号
}t[X<<2|1];
void build(int p,int l,int r){
t[p]={l,r};
if(l==r){
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
bool leq(double a,double b){
return a-b<-eps;
}
bool geq(double a,double b){
return a-b>eps;
}
bool eq(double a,double b){
return abs(a-b)<=eps;
}
bool check(int u,int v,int x){
double yU=a[u](x),yV=a[v](x);
return geq(yU,yV)||eq(yU,yV)&&u<v;
}
void down(int p,int x){
int &id=t[p].id;
if(t[p].l==t[p].r){
if(check(x,id,t[p].l)){
id=x;
}
return;
}
if(check(x,id,t[p<<1].r)){
swap(x,id);
}
if(check(x,id,t[p<<1].l)){
down(p<<1,x);
}
if(check(x,id,t[p<<1|1].r)){
down(p<<1|1,x);
}
}
void update(int p,int l,int r,int x){
if(l<=t[p].l&&t[p].r<=r){
down(p,x);
return;
}
if(l<=t[p<<1].r){
update(p<<1,l,r,x);
}
if(t[p<<1|1].l<=r){
update(p<<1|1,l,r,x);
}
}
void chkmax(int &u,int v,int x){
double yU=a[u](x),yV=a[v](x);
if(yV>yU){
u=v;
}else if(eq(yU,yV)&&v<u){
u=v;
}
}
int query(int p,int x){
int ans=t[p].id;
if(t[p].l==t[p].r){
return ans;
}
if(x<=t[p<<1].r){
chkmax(ans,query(p<<1,x),x);
}else{
chkmax(ans,query(p<<1|1,x),x);
}
return ans;
}
}t;
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;
cin>>n;
t.build(1,1,X);
int lastans=0;
for(int i=1,id=0;i<=n;i++){
int op,k;
node a,b;
cin>>op;
switch(op){
case 0:
cin>>k;
k=(k+lastans-1)%X+1;
lastans=t.query(1,k);
cout<<lastans<<'\n';
break;
case 1:
cin>>a.x>>a.y>>b.x>>b.y;
a.x=(a.x+lastans-1)%X+1;
a.y=(a.y+lastans-1)%Y+1;
b.x=(b.x+lastans-1)%X+1;
b.y=(b.y+lastans-1)%Y+1;
if(a.x>b.x){
swap(a,b);
}
id++;
::a[id]=line(a,b);
t.update(1,a.x,b.x,id);
break;
}
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}
李超线段树维护斜率优化 DP
李超线段树同样可以用于维护斜率优化 DP,并且可以处理决策点不单调的情况。
参见此处。