首页 > 其他分享 >李超线段树学习笔记

李超线段树学习笔记

时间:2024-03-02 22:11:09浏览次数:34  
标签:int 线段 tree 李超 笔记 编号 calc

前言

如有错误,欢迎各位大佬指出。

GM说学了斜率和线段树就可以尝试。

前置芝士:

  • 斜率

  • 线段树

1.什么是李超线段树?

李超线段树主要解决平面坐标系内有关直线的问题,李超线段树是一种特殊的线段树

这里给出一个引例 P4097 [HEOI2013]Segment

题目大意及要维护两个操作:

  • 给定一条线段的左端点和右端点。

  • 给定条直线 \(x=k\),求与该直线相交的线段的最大 \(y\) 值是多少。

对于这个题,如果要用普通线段树维护,那么我们就必须采用权值线段树,而且很难讲左右子树进行合并,并不方便。因此,这个题就需要用到李超线段树了。

2.李超线段树的具体做法。

显然,对于这道题,无论如何都是要采用权值线段树的。但是我们在维护的时候,可以有一些不同的做法。

首先,我们以 \(x\) 轴为权值,维护一颗权值线段树,对于每一个节点,记录该节点上,\(y\) 值最大的线段的编号。

然后,我们在修改的时候,找到线段的定义域所完全覆盖的所有区间,然后拿出来进行单独维护。

对于单独维护的区间 \([l,r]\),我们找到中点 \(mid\),假设新加入的线段编号为 \(y\)。

如果当前区间并没有最大的线段,则将其设为 \(y\)。

接着,比较 \(x=mid\) 时,比较该区间原来最大的线段和新加入的线段的大小,如果新的线段更大,则将原来的编号和现在的编号直接交换。(可以发现无论如何,交不交换,现在编号都是与现在维护的节点最大值不同的那一个)

接着,我们就要看不能成为该区间最大值的编号(即现在的编号)可不可能成为该区间子节点的最大值。

如果在 \(x=l\)(\(l\) 为区间左端点)时,现在的编号得到的值大于原来的值,则显然该区间节点的左子树的左右端点的最大值都由现在的编号产生,所以现在的编号就能成为左子树的最大值,就向下递归。

当判断需不需要递归右子树时也同理。

最后注意两个细节。

  • 当 \(y\) 值相同时,是求编号最小的线段,所以在判断需不需要递归的时候,要判断 \(y\) 值相等时的情况。

  • 在求线段解析式时,如果 \(x_1=x_2\) 要特判。

代码

#include<bits/stdc++.h>
using namespace std;
int n;
int op,k;
int x[100005],y[100005],x2[100005],y2[100005];
struct line
{
	double k,b;
}arr[100005];
double calc(int i,int x)
{
	return arr[i].k*(double)x+arr[i].b;
}
int tot;
void adds(int i)
{
	++tot;
	if(x[i]==x2[i]) arr[tot].k=0,arr[tot].b=max(y[i],y2[i]);
	else arr[tot].k=1.0*(y2[i]-y[i])/(x2[i]-x[i]),arr[tot].b=y[i]-arr[tot].k*x[i];
}
struct Li_chao_tree
{
	int l,r;
	int id;
}tree[200005];
int cnt;
void build(int p,int l,int r)
{
	tree[p].l=l,tree[p].r=r;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
}
void update(int p,int x)
{	
	int mid=(tree[p].l+tree[p].r)>>1;
	if(calc(x,mid)>calc(tree[p].id,mid)) swap(tree[p].id,x);
	int y=tree[p].id; 
	if(calc(x,tree[p].l)>calc(y,tree[p].l)||(calc(x,tree[p].l)==calc(y,tree[p].l)&&x<y)) update(p<<1,x);
	if(calc(x,tree[p].r)>calc(y,tree[p].r)||(calc(x,tree[p].r)==calc(y,tree[p].r)&&x<y)) update(p<<1|1,x);
}
void change(int p,int l,int r,int x)
{
	if(tree[p].r<l||tree[p].l>r) return;
	if(tree[p].l>=l&&tree[p].r<=r)
	{
		update(p,x);
		return;
	}
	change(p<<1,l,r,x);
	change(p<<1|1,l,r,x);
}
pair<double,int> pmax(pair<double,int> a,pair<double,int> b)
{
	if(fabs(a.first-b.first)<1e-9) return min(a,b);
	else return max(a,b);
}
pair<double,int> query(int p,int x)
{
	if(tree[p].l>x||tree[p].r<x) return make_pair(0,0);
	if(tree[p].l==tree[p].r) return make_pair(calc(tree[p].id,x),tree[p].id);
	int y=tree[p].id;
	pair<double,int> now; 
	if(y) now=make_pair(calc(tree[p].id,x),tree[p].id);
	else now=make_pair(0,0);
	return pmax(now,pmax(query(p<<1,x),query(p<<1|1,x)));
}
signed main()
{
	scanf("%d",&n);
	int lastans=0;
	build(1,1,39989);
	for(int i=1;i<=n;++i)
	{
		int op;
		scanf("%d",&op);
		if(op==0)
		{
			scanf("%d",&k);
			k=(k+lastans-1+39989)%39989+1;
			printf("%d\n",lastans=query(1,k).second);
		}
		else
		{
			scanf("%d%d%d%d",&x[i],&y[i],&x2[i],&y2[i]);
			x[i]=(x[i]+lastans-1+39989)%39989+1;
			y[i]=(y[i]+lastans-1+(int)1e9)%(int)(1e9)+1;
			x2[i]=(x2[i]+lastans-1+39989)%39989+1;
			y2[i]=(y2[i]+lastans-1+(int)1e9)%(int)(1e9)+1;
			adds(i);
			change(1,min(x[i],x2[i]),max(x[i],x2[i]),tot);	
		}
	}
}

3.李超线段树的时间复杂度

首先,对于建树和查询操作,都是标准的线段树,单次操作时间复杂度都为 \(log2(n)\)。

然后,对于修改操作,由于我们第一步需要找到给区间完全覆盖的所有节点,需要 \(log2(n)\) 然后还需要在每个区间进行更新答案,又需要 \(log2(n)\) 的时间,所以修改操作的时间就为 \(log2(n)^2\)。

4.李超线段树的应用

  • 在引入中有说可以解决平面坐标系内的问题

  • 仔细思考可以发现,斜率优化是要维护截距的最大或最小,让我们就可以把所有的直线放进去,然后看在 \(x=0\) 时,\(y\) 值得最大最小的编号是多少,还是很板的。唯一的缺点就是时间复杂度多了 \(log2(n)\),并且码长变大了,不过倒是解决了分不清楚小于大于符号已及二分的问题。

相关例题

  • 普通李超线段树:

P4254 [JSOI2008]Blue Mary开公司

P4069 [SDOI2016]游戏(结合树链剖分)

  • 斜率优化:

P4655 [CEOI2017]Building Bridges

P5785 [SDOI2012]任务安排

标签:int,线段,tree,李超,笔记,编号,calc
From: https://www.cnblogs.com/SFsaltyfish/p/18049360

相关文章

  • 随笔记录篇——C++iostream库 以及std
    这篇文章非原创,来自我学习过程中看到的其他博主发的一些资料,解决了我的疑问,在此进行整理。C语言的标准输入输出库是stdio.h库,是一个函数库,而不是类库。其中包含了我们其中所用的scanfpringf都是一些独立的全局函数,因为C语言是不支持类的。C++的标准输入输出库iostream是一个类......
  • 点分治学习笔记
    0.前言又称淀粉质。学科营之前赶紧来一波急抓。1.引入我们考虑这样一个问题,对于一棵树,我们求出树上所有路径长度小于等于\(k\)的路径总数。首先不难想到一种\(n^3\)的暴力做法,即枚举两个端点,然后暴力出路径。考虑找路径的时候优化一下,采用倍增或者树链剖分将复杂度变为......
  • 高级搜索算法学习笔记
    0.前言如有错误,欢迎各位大佬指出。前置芝士:深度优先搜索广度优先搜索1.何为高级搜索?在通常情况下,普通的深搜往往会超时,即使剪枝也无动于衷。对于广搜,我们一旦超时也很难进行优化。而这时,我们就需要对搜索的形态进行改变,将深搜和广搜进行升级,变形成为各种效率更高的高......
  • 基础笔记-时空复杂度分析
    C++一秒可以算1e7或者1e8的次数由数据范围反推算法复杂度以及算法内容-AcWing第七章时空复杂度分析笔记-AcWing  时间复杂度分析1.纯循环,看几层循环,就是几次方(有些循环不一定,比如双指针,i循环n次,j其实一共只会循环n次,所以复杂度是O(n))2.递归,看有递归了几层,计算每一层......
  • 《A Hierarchical Framework for Relation Extraction with Reinforcement Learning》
    代码原文地址摘要现有的大多数方法在确定关系类型之前,需要先识别出所有的实体,这样就忽略了实体提及和关系类型之间的交互。本文提出了一种新颖的联合抽取范式,把相关实体看作是关系的参数(首先检测一个关系,然后提取相应的实体作为关系的参数)。本文在这个范式下采用了一个分层......
  • Docker学习笔记-01-ubuntu22.04安装Docker Desktop
    Docker学习笔记-01-ubuntu22.04安装DockerDesktopubuntudocker一、安装前的说明DockerDesktopforLinux和LinuxDockerEngine是两个不同的东西,在使用的时候会有不同,但是有什么不同,我还没有具体去了解,在后面学习使用的时候要注意区分。DockerDesktopforLinux需要Virtual......
  • Living-Dream 系列笔记 第34期
    T1有一个比较秒的trick:虚拟点。对于本题,我们设一虚拟点\(n+1\)表示水源,于是打井的操作即为与点\(n+1\)连边,将点权转为边权。然后跑kruskal即可。#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;intn,tot;intfa[331];intw[331];intp[331]......
  • Living-Dream 系列笔记 第33期
    Living-Dream系列笔记强势回归(雾)T1并查集妙妙题。很容易想到一种朴素的贪心策略:对于所以物品按照价格从大到小排序,然后每一个物品,找到最晚的没有卖物品的那一天卖掉此物品。这样贪心的时间复杂度为\(O(\maxd_i\timesn)\),可以通过。考虑如何优化此贪心。可以发现朴素......
  • Living-Dream 系列笔记 第38期
    T1floyd模板。#include<bits/stdc++.h>usingnamespacestd;intn,m;intdp[131][131];voidfloyd(){ for(intk=1;k<=n;k++) for(inti=1;i<=n;i++) for(intj=1;j<=n;j++) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);}intmain(){ cin&g......
  • Living-Dream 系列笔记 第46期
    强连通分量(StronglyConnectedComponents,SCC)。强连通:有向图中,\(x,y\)能相互到达。弱连通:有向图中,\(x\)能到\(y\),\(y\)不能到\(x\)(或反之)。强连通分量:有向图\(G\)中一极大子图\(G1\),使得\(G1\)任意两点均强连通,且\(G1\)不可变得更大(不能添加点)。强连通分量要么是......