1 前言之前
感觉最近代码能力极其低下啊
而自己又在学新的东西
于是就有了苦果,代码总是出小错误,调不出来
然后就要花很多时间
看来还需提升思维缜密性
2 前言
李超线段树实在mangouyang的博客中看到的,看起来算法非常良心,算法并不是特别难,主要在于其思想
它有个经典问题:
支持在线,有两种操作
- 第一种操作:在平面中加入一条线段
- 第二种操作:给定
a
a
a,求与
x
=
a
x=a
x=a直线相交的线段中相交点
y
y
y值最大/小的点的编号是多少(这篇博客我默认用最大来讲,因为有个例题[bzoj3165][heoi2013]Segment)
(所有输入都是整数)
3 思路
考虑维护一棵线段树,我们知道,线段树的一个节点代表着一个区间,我们另在这个节点上的值为一个线段的标号,用来存储当前区间的优势线段
定义一个区间的优势线段,指这条线段的
x
x
x坐标范围覆盖整个区间,并且在区间里面的某个位置上可能取到那个位置的最大值,如果包含某个位置的所有区间的优势线段中没有出现线段
a
a
a,那么
a
a
a一定不是查询这个位置的答案
我们考虑如何维护这个信息
3.1 插入操作
- 如果只有一次操作该咋样?
由于成为优势线段的前提是这条线段的 x x x坐标范围覆盖整个区间,那么我们就像写线段树的区间操作一样,进行区间赋值就好了,复杂度 Θ ( l o g n ) \Theta(logn) Θ(logn) - 多次操作
考虑多次操作我们一样可以像一次操作一样做,但是这个时候会发现有个问题,如果某个区间我要进行赋值,但是它原本就有值,我不能直接覆盖上去,这该怎么办呢?
这个时候就需要进行一些处理
3.2 add操作
定义add操作为加入一条线段更新某个区间的答案
相当于现在有两个线段
a
a
a和
b
b
b,现在他们要只留下一个,并且保持优势线段的性质
由于这是线段树,所以如果要维护信息,可以进行push_down的骚操作,但是两边都push_down的复杂度是不对的,会变成
Θ
(
区
间
长
度
)
\Theta(区间长度)
Θ(区间长度),所以我们要减少push_down
首先我们考虑这样一件事情,如果在这段区间的每一个位置一条线段都在另一条线段的上方,那我们就直接保留上方的那条线段,不用push_down,因为下方的线段已经不可能成为答案了。特殊的,在区间长度为1的时候一定符合这个条件,所以长度为1的区间一定不用push_down
这张图中,
s
e
g
m
e
n
t
1
segment1
segment1严格比
s
e
g
m
e
n
t
2
segment2
segment2优,所以显然
s
e
g
m
e
n
t
2
segment2
segment2可以舍去
如果不满足上述条件,那么两个线段一定在这个区间中有交点,也就是说两条线段都符合优势线段的条件,都要保留
一个区间不能保留两个值,所以一定要push_down
然后我们又发现:如果一个线段在某个区间中的贡献只在左半边或者是右半边,那么它就可以被push_down
容易发现,给出的两条线段,至少有一条满足条件,上图
我们可以求出两条线段的交点,容易发现,在交点左边,
s
e
g
m
e
n
t
2
segment2
segment2高,在交点右边,
s
e
g
m
e
n
t
1
segment1
segment1高,由于交点在
m
i
d
mid
mid左边,那么我们就可以保留
s
e
g
m
e
n
t
1
segment1
segment1,将
s
e
g
m
e
n
t
2
segment2
segment2push_down到左儿子,其它情况同理
考虑复杂度,由于一条线段在线段树中有贡献的节点数是
Θ
(
l
o
g
n
)
\Theta(logn)
Θ(logn)的,一次贡献最多被push_down树高(
Θ
(
l
o
g
n
)
\Theta(logn)
Θ(logn))次,总复杂度是
Θ
(
l
o
g
2
n
)
\Theta(log^2n)
Θ(log2n)
至此,我们就解决了这个地方的问题
3.3 查询操作
给出一个坐标
由于我们维护了优势线段的的特征,所以单次查询就直接把logn个节点取最优即可
4 实现
考虑我们如何实现上述过程,我们只需要支持两个功能,一个是求一个线段在某个横坐标下的值,一个是线段求交,由于我给出的例题要求相同情况下取编号较小的,所以还要加
e
p
s
eps
eps的特判
我们只要求两条线段在两个端点的大小关系,如果相同说明是前面说的第一种情况,否则是第二种,第二种情况进行一次线段求交即可
此外还要特判斜率为
+
inf
+\inf
+inf的情况
(垃圾数据,没有这些特判照样通过此题)
代码不是很清真,贴出来吧
// luogu-judger-enable-o2
#include<cstdio>
#include<cctype>
#include<algorithm>
namespace fast_IO
{
const int IN_LEN=10000000,OUT_LEN=10000000;
char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
char cu=getchar();x=0;bool fla=0;
while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
if(x>=10)printe(x/10);
putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
if(x<0)putchar('-'),printe(-x);
else printe(x);
}
const int MAX=131073;const double eps=1e-7;
int n,lastans;
struct segment
{
int x0,y0,x1,y1;
inline double calc(const int place)const
{
if(x0==x1)return y1;
return (double)(y1-y0)/(double)(x1-x0)*(place-x0)+y0;
}
}Q[100001];int tot;
inline double Cross(const segment&x,const segment&y)
{
return (double)x.x0+(double)abs((double)x.y0-(double)y.calc(x.x0))/abs((double)(x.y1-x.y0)/(double)(x.x1-x.x0)-(double)(y.y1-y.y0)/(double)(y.x1-y.x0))+eps;
}
int l[MAX],r[MAX],mid[MAX],id[MAX];
inline void ini(const int root,const int ll,const int rr)
{
l[root]=ll,r[root]=rr,mid[root]=(ll+rr)>>1;
if(ll==rr)return;
ini(root<<1,ll,mid[root]),ini(root<<1|1,mid[root]+1,rr);
}
inline void add(const int root,const int ins)
{
if(!id[root])id[root]=ins;
else if(l[root]==r[root])
{
const double v1=Q[ins].calc(l[root]),v2=Q[id[root]].calc(l[root]);
if(abs(v1-v2)<eps)mind(id[root],ins);
if(v1>v2)id[root]=ins;
}
else
{
const int u=id[root];
const bool lsign=Q[ins].calc(l[root])<=Q[u].calc(l[root]),rsign=Q[ins].calc(r[root])<=Q[u].calc(r[root]);
if(lsign==rsign)
{
if(!lsign)id[root]=ins;
}
else
{
double PL=Cross(Q[ins],Q[u]);
if(abs(PL-((int)PL))<eps&&lsign==0)PL-=1;
const int pl=PL;
if(pl<=mid[root])
{
if(lsign)id[root]=ins,add(root<<1,u);
else add(root<<1,ins);
}
else
{
if(rsign)id[root]=ins,add(root<<1|1,u);
else add(root<<1|1,ins);
}
}
}
}
void insert(const int root,const int ll,const int rr,const int ins)
{
if(l[root]==ll&&r[root]==rr)
{
add(root,ins);
return;
}
if(rr<=mid[root])insert(root<<1,ll,rr,ins);
else if(ll>mid[root])insert(root<<1|1,ll,rr,ins);
else insert(root<<1,ll,mid[root],ins),insert(root<<1|1,mid[root]+1,rr,ins);
}
int search(const int root,const int wan)
{
if(l[root]==r[root])return id[root];
int res=0;
if(wan<=mid[root])res=search(root<<1,wan);
else res=search(root<<1|1,wan);
const double v1=Q[id[root]].calc(wan),v2=Q[res].calc(wan);
if(abs(v1-v2)<eps)return min(res,id[root]);
if(v1>v2)return id[root];
return res;
}
int main()
{
read(n);
ini(1,1,39989);
while(n--)
{
int opt;read(opt);
if(opt==0)
{
int x;read(x),x=(x+lastans-1)%39989+1;
lastans=search(1,x),print(lastans),putchar('\n');
}
else
{
int X0,Y0,X1,Y1;
read(X0),X0=(X0+lastans-1)%39989+1;
read(Y0),Y0=(Y0+lastans-1)%1000000000+1;
read(X1),X1=(X1+lastans-1)%39989+1;
read(Y1),Y1=(Y1+lastans-1)%1000000000+1;
if(X0>X1||(X0==X1&&Y0>Y1))swap(X0,X1),swap(Y0,Y1);
Q[++tot]=(segment){X0,Y0,X1,Y1};
insert(1,Q[tot].x0,Q[tot].x1,tot);
}
}
return flush(),0;
}
- 然后其实还有个更清真的写法,不用线段求交,直接求在
m
i
d
mid
mid位置上的大小关系
这个写法在我的这种写法下并不快,就只贴代码网址(这个是洛谷的提交记录,你需要AC后才能查看代码)了 - 当然有更清真的算法,就是不要存点,直接存直线的一半表达式 y = k x + b y=kx+b y=kx+b里的 k , b k,b k,b两个值,这样鲁棒性大大下降,但是速度有显著提升,同样只贴出代码网址
5 总结
这就是李超线段树了,似乎并不难,但是一般考试给出的题都不会是裸题,这个算法学习一下可以当 i d e a idea idea ^ _ ^