李超线段树小结

废话

这是一个用来维护一些二维平面上线段的信息的数据结构。

思想不复杂,代码不难写,也不像某同行吉司机线段树一样较难分析复杂度,还是很容易学会的。

这种大概人人都会的东西就只有我这个蒟蒻现在才学了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],那么就可以考虑更新这个节点的最优势线段。

更新需要分类讨论一下:

  1. 假如这个点没有最优势线段,直接替换即可。
  2. 假如新线段完全在原最优势线段上方,也直接替换。
  3. 假如新线段完全在原最优势线段下方,则直接返回。
  4. 最后一种情况就是两者相交,此时看一下在 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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值