废话
这是一个用来维护一些二维平面上线段的信息的数据结构。
思想不复杂,代码不难写,也不像某同行吉司机线段树一样较难分析复杂度,还是很容易学会的。
这种大概人人都会的东西就只有我这个蒟蒻现在才学了qwq……
正题
例题长这个样子:
你需要维护两种操作:1、增加一条线段;2、给出 c c c,询问直线 x = c x=c x=c 经过的线段中,最上面那条线段是谁。
在有些题中,询问的不是线段是谁
,而是与该线段交点的y坐标
是多少,但都是一样的。
考虑用线段树来维护 x x x 坐标,在线段树的每个节点上,维护一个最优势线段,这条线段满足:在该节点管理的区间 [ l , r ] [l,r] [l,r] 内, x = m i d x=mid x=mid 时 y y y 值最大。
然后考虑如何插入一条线段,假设这条线段的 x x x 坐标在 [ a , b ] [a,b] [a,b] 区间内,先将这个 [ a , b ] [a,b] [a,b] 放到线段树上递归,跑到一个节点上时,假如 [ a , b ] [a,b] [a,b] 完全覆盖这个节点管理的区间 [ l , r ] [l,r] [l,r],那么就可以考虑更新这个节点的最优势线段。
更新需要分类讨论一下:
- 假如这个点没有最优势线段,直接替换即可。
- 假如新线段完全在原最优势线段上方,也直接替换。
- 假如新线段完全在原最优势线段下方,则直接返回。
- 最后一种情况就是两者相交,此时看一下在 m i d mid mid 处的取值谁更大,大的成为最优势线段,另外一个继续递归下去。
最特别的就是第四种情况,递归的方法是看交点的 x x x 坐标,假如交点的 x x x 坐标在 [ l , m i d ] [l,mid] [l,mid] 内,则去左子树内递归,否则去右子树。
原因的话看图会很容易理解:
当前最优势线段为蓝色线段,而红色线段是要递归下去的线段,他们的交点 > m i d >mid >mid,所以就应该去右子树递归,因为已知蓝色线段在 m i d mid mid 处取值大于红色线段,所以在没有交点的一边,红色线段不可能取值比蓝色线段大,就没有递归的必要。
这是代码,这个函数是写在结构体内部的,所以:
struct func{
int l,r;
double k,b;
double calc(int x){
return k*x+b;}
};
double cross(func x,func y){
return (y.b-x.b)/(x.k-y.k);}//求两直线交点的x坐标
#define zuo ch[0]
#define you ch[1]
struct node{
int l,r,mid;func z;node *ch[2];
//省略了建树部分
void insert(func x){
if(x.l<=l&&x.r>=r){
//线段完全覆盖该区间
if(z.l==-1)z=x;//没有最优势线段
//x的两端都比z高,则x完全在z上面
else if(x.calc(l)-z.calc(l)>eps&&x.calc(r)-z.calc(r)>eps)z=x;
//否则看是否有交点
else if(x.calc(l)-z.calc(l)>eps||x.calc(r)-z.calc(r)>eps){
if(x.calc(mid)-z.calc(mid)>eps)swap(x,z);//看mid处谁取值大
ch[cross(x,z)-mid>eps]->insert(x);//去交点所在区间继续递归
}
}else{
if(x.l<=mid)zuo->insert(x);
if(x.r>=mid+1)you->insert(x);
}
}
}*root=NULL;
分析一下复杂度,首先看insert
函数内的大体结构:
if(x.l<=l&&x.r>=r){
...
//一类递归
}else{
...
//二类递归
}
插入一条线段时,这条线段会被二类递归
分解成至多 log \log log 段,每一段会进入一类递归
递归至多 log \log log 次,所以复杂度是 log 2 \log^2 log2 的,然而这个也很难跑满,所以实际上十分快。
询问的时候,在线段树上一路从 [ 1 , n ] [1,n] [1,n] 走到 [ x , x ] [x,x] [x,x],求出经过的所有节点上的最优势线段在 x x x 处的取值即可。
//这个函数也在结构体内部
double ask(int x){
double re=(z.l!=-1?z.calc(x):0);
if(l==r)return re;
return max(re,ch[x>=mid+1]->ask(x));
}
这题是维护直线,所以修改一次的复杂度要更小,为 log \log log 而不是 log 2 \log^2 log2。
完整代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define eps 1e-8
int n;
struct func{
int l,r;
double k,b;
double calc(int x){
return k*x+b;}
};
double cross(func x,func y){
return (y.b-x.b)/(x.k-y.k);}
#define zuo ch[0]
#define you ch[1]
struct node{
int l,r,mid;func z;node *ch[2];
node(int x,int y):l(x),r(y),mid(l+r>>1){
z.l=-1;if(x<y){
zuo=new node(l,mid);
you=new node(mid+1,r);
}else zuo=you=NULL;
}
void insert(func x){
if(x.l<=l&&x.r>=r){
if(z.l==-1)z=x;
else if(x.calc(l)-z.calc(l)>eps&&x.calc(r)-z.calc(r)>eps)z=x;
else if(x.calc(l)-z.calc(l)>eps||x.calc(r)-z.calc(r)>eps){
if(x.calc(mid)-z.calc(mid)>eps)swap(x,z);
ch[cross(x,z)-mid>eps]->insert(x);
}
}else{
if(x.l<=mid)zuo->insert(x);
if(x.r>=mid+1)you->insert(x);
}
}
double ask(int x){
double re=(z.l!=-1?z.calc(x):0);
if(l==r)return re;
return max(re,ch[x>=mid+1]->ask(x));
}
}*root=NULL;
int main()
{
scanf("%d",&n);
root=new node(1,5e4);
for(int i=1;i<=n;i++){
char s[10];scanf("%s",s);
if(s[0]=='P'){
func x;x.l=1;x.r=5e4;
scanf("%lf %lf",&x.b,&x.k);x.b-=x.k;
root->insert(x);
}else{
int x;scanf("%d",&x);
printf("%d\n",(int)root->ask(x)/100);
}
}
}
题表
说是题表其实目前只有一题……
2020牛客NOIP赛前集训营-提高组(第一场)D-牛牛的RPG游戏 题解
以后遇到了什么有趣的题大概还会放上来的qwq。