这个讲的不错:算法|李超线段树初步(算法讲解+例题)
用处:用于处理二维平面内的最优势线段(或直线),一般支持两种操作,向二维平面插入一条新的线段(或直线),询问x=a与所有的线段(或直线)的交点的最大(最小值)。
假设我们插入的是一条直线,他的方程为:
y
=
a
x
+
b
y=ax+b
y=ax+b。
线段树的区间下标代表x坐标。
那么插入的情况大体有四种:
这里就最大值来讨论。
- 若当前区间内还没有最优势线段的话,那新的直线直接就可以插入进来。
- 若新插入的直线在该区间内所有的取值均大于已存在的最优势直线的话,那么新的直线可以完全替代他。
- 若新插入的直线的取值均小于最优势直线的话,那说明新插入的直线在该区间内永远都不可能成为最优势直线,直接结束函数即可。
- 若新插入的直线与原先的最优势直线有交点的话,我们选择在中点处y值较大的作为最优势直线,然后判断交点的位置是在mid左边还是右边,去对应的区间更新即可,因为下一次的优劣更新只会发生在有交点的区间,这个画画图就清楚了。
如下:
const int maxn=5e4+7;
const double epos=1.0e-7;
struct Line{
double a;
double b;
}temp;//y=ax+b;
Line line[maxn<<2|1];//最优势直线;
bool f[maxn<<2|1];
//获取直线在x处的y值;
double gety(Line line,int x){
return x*line.a+line.b;
}
//获取两直线的交点;
double getx(Line a,Line b){
return(a.b-b.b)/(b.a-a.a);
}
void updata(int l,int r,int k,Line in){
if(!f[k]){//当前区间还没有最优势线段,直接赋值;对应情况1;
line[k]=in;
f[k]=1;
return ;
}
int mid=(l+r)>>1;
//新的线段在该区间的取值均大于原最优线段,替换之;对应情况2;
if(gety(in,l)-gety(line[k],l)>epos&&gety(in,r)-gety(line[k],r)>epos) line[k]=in;
//存在交点,只在某一部分最优;对应情况4;
else if(gety(in,l)-gety(line[k],l)>epos||gety(in,r)-gety(line[k],r)>epos){
//取中点处优势的线段,将劣势线段下传更新其他区间;
if(gety(in,mid)-gety(line[k],mid)>epos) swap(in,line[k]);
if(mid-getx(line[k],in)>epos) updata(l,mid,k<<1,in);
else updata(mid+1,r,k<<1|1,in);
}
//else对应情况3
}
对于查询函数,直接查可以包含x的所有区间的最值即可:
double myfind(int l,int r,int k,int x){
if(l==r) return gety(line[k],x);
int mid=(l+r)>>1;
double res=gety(line[k],x);
if(x<=mid) res=max(res,myfind(l,mid,k<<1,x));
else res=max(res,myfind(mid+1,r,k<<1|1,x));
return res;
}
如果我们插入的是线段的话,只需要在Line结构体内增加线段的横坐标取值范围,然后更新操作就相当于是线段树的区间更新,就像是下面这个样子。
void updata(int l,int r,int k,int L,int R){
if(l>=L&&r<=R){
//...与上面一致;
}
int mid=(l+r)>>1;
if(L<=mid) updata(l,mid,k<<1,L,R);
if(R>mid) updata(mid+1,r,k<<1|1,L,R);
}
题目链接:bzoj1568
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e4+7;
const double epos=1.0e-7;
struct Line{
double a;
double b;
}temp;
Line line[maxn<<2|1];
bool f[maxn<<2|1];
double gety(Line line,int x){
return x*line.a+line.b;
}
double getx(Line a,Line b){
return(a.b-b.b)/(b.a-a.a);
}
void build(int l,int r,int k){
line[k].a=line[k].b=0;
f[k]=0;
if(l==r) return ;
int mid=(l+r)>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
}
void updata(int l,int r,int k,Line in){
if(!f[k]){//当前区间还没有最优势线段,直接赋值;
line[k]=in;
f[k]=1;
return ;
}
int mid=(l+r)>>1;
//新的线段在该区间的取值均大于原最优线段,替换之;
if(gety(in,l)-gety(line[k],l)>epos&&gety(in,r)-gety(line[k],r)>epos) line[k]=in;
//存在交点,只在某一部分最优;
else if(gety(in,l)-gety(line[k],l)>epos||gety(in,r)-gety(line[k],r)>epos){
//取中点处优势的线段,将劣势线段下传更新其他区间;
if(gety(in,mid)-gety(line[k],mid)>epos) swap(in,line[k]);
if(mid-getx(line[k],in)>epos) updata(l,mid,k<<1,in);
else updata(mid+1,r,k<<1|1,in);
}
}
double myfind(int l,int r,int k,int x){
if(l==r) return gety(line[k],x);
int mid=(l+r)>>1;
double res=gety(line[k],x);
if(x<=mid) res=max(res,myfind(l,mid,k<<1,x));
else res=max(res,myfind(mid+1,r,k<<1|1,x));
return res;
}
char ss[9];
int main(){
int t;
double s,p;
int x;
build(1,50000,1);
scanf("%d",&t);
while(t--){
scanf("%s",ss);
if(ss[0]=='Q'){
scanf("%d",&x);
//cout<<myfind(1,50000,1,x)<<endl;
printf("%lld\n",(ll)(myfind(1,50000,1,x)/100.0));
}
else{
scanf("%lf%lf",&s,&p);
temp.a=p,temp.b=s-p;
//cout<<temp.a<<" "<<temp.b<<endl;
updata(1,50000,1,temp);
}
}
return 0;
}
当然我们还有更普及的板子,对于线段来说,他有横坐标的取值范围:
以最大值为例。
const int maxn=5e4+7;
const double epos=1.0e-7;
struct Line{
double a;
double b;
int l,r;//横坐标范围;
}temp;
Line line[maxn<<2|1];
double gety(Line line,int x){
return x*line.a+line.b;
}
double getx(Line a,Line b){
return(a.b-b.b)/(b.a-a.a);
}
void build(int l,int r,int k){
line[k].a=line[k].b=0;//根据题目的需要可以赋值为极小值;
line[k].l=1,line[k].r=50000;//初始化可以直接给足范围;
if(l==r) return ;
int mid=(l+r)>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
}
void updata(int l,int r,int k,Line in){
if(l>=in.l&&r<=in.r){
//由于我们已经初始化过了,故这个条件可以直接略去;
// if(!f[k]){//当前区间还没有最优势线段,直接赋值;
// line[k]=in;
// f[k]=1;
// return ;
// }
int mid=(l+r)>>1;
//新的线段在该区间的取值均大于原最优线段,替换之;
if(gety(in,l)-gety(line[k],l)>epos&&gety(in,r)-gety(line[k],r)>epos) line[k]=in;
//存在交点,只在某一部分最优;
else if(gety(in,l)-gety(line[k],l)>epos||gety(in,r)-gety(line[k],r)>epos){
//取中点处优势的线段,将劣势线段下传更新其他区间;
if(gety(in,mid)-gety(line[k],mid)>epos) swap(in,line[k]);
if(mid-getx(line[k],in)>epos) updata(l,mid,k<<1,in);
else updata(mid+1,r,k<<1|1,in);
}
}
else{
int mid=(l+r)>>1;
if(in.l<=mid) updata(l,mid,k<<1,in);
if(in.r>mid) updata(mid+1,r,k<<1|1,in);
}
}
double myfind(int l,int r,int k,int x){
if(l==r) return gety(line[k],x);
int mid=(l+r)>>1;
double res=gety(line[k],x);
if(x<=mid) res=max(res,myfind(l,mid,k<<1,x));
else res=max(res,myfind(mid+1,r,k<<1|1,x));
return res;
}