首页 > 其他分享 >李超线段树

李超线段树

时间:2024-08-14 15:49:02浏览次数:13  
标签:int 线段 李超 mid x0 calc lin

用途:

用于二维坐标系维护多条线段。

算法:

本质上是采用标记永久化,
对每个线段树节点维护一个标记表示该区间存在这一条线段,
查询时从上到下经过节点的标记即为该横坐标上可能经过的线段。
下面需在标记(线段)间的比较上作考虑:建议画图理解
此时对于一个区间\([l, r]\),
找出中点\(mid\),以两线段(新线段,原标记)在中点大小关系分:下的为\(f\),上的为\(g\)(等于时都可)。

下面约定\(f[l]\)为在\(f\)线段上\(x=l\)时的纵坐标,
先给该点打上标记\(g\)。
如果\(f\)被\(g\)全面覆盖,即有\(f[l] < g[l], f[r] < g[r]\),那么不需考虑\(f\)向下,返回。
如果\(f[l] > g[l]\),说明\(f\)在左半部分还是有优势,相对在右半部分一定无优势,所以向\([l, mid]\)递归传下\(f\)。
反之,如果\(f[r] > g[r]\),向\([mid+1, r]\)递归传下\(f\)即可。

全局上,在修改时,先找出线段范围对应线段树节点,在从节点开始向下做标记比较。
有\(log_2n\)个节点,递归也是深度即\(log_2n\)级,所以总复杂度为\(O(nlog_2^2n)\)。

模版题代码:P4097 【模板】李超线段树 / [HEOI2013] Segment

细节部分:
注意要求相同纵坐标还要按编号小输出,不妨直接用编号作标记,
同时等于也要考虑下传
数据范围需要long double

点击查看代码
#include<algorithm>
#include<stdio.h>
#define ll long long
#define N 100005
#define db long double
using namespace std;
ll read() {
	char c=getchar(); ll x=0, y=1;
	while(!(c >= '0' && c <= '9' || c == '-')) c=getchar();
	if(c == '-') y=-1, c=getchar();
	while(c >= '0' && c <= '9') x=x*10+c-'0', c=getchar();
	return x*y;
}
int n;

struct line {
	db k, b;
} li[N];

db calc(int x, ll pos) {
	return li[x].k * pos + li[x].b;
}
struct SMT {
	struct node {
		int l, r;
		int lin;
	} t[N<<2];
	int ls(int x) {return x<<1;}
	int rs(int x) {return x<<1|1;}
	void tree_pre(int x, int l, int r) {
		t[x].l=l, t[x].r=r;
		if(l == r) return;
		int mid = (l+r) >> 1;
		tree_pre(ls(x), l, mid);
		tree_pre(rs(x), mid+1, r);
	}
	void tagd(int x, int l, int r, int seg) {
		int mid = (l+r) >> 1;
		int f = seg, g = t[x].lin;
		if(calc(f, mid) > calc(g, mid)) swap(f, g);
//		printf("%d %d %.2lf %.2lf %.2lf %.2lf\n", l, r, calc(f, l), calc(f, r), calc(g, l), calc(g, r));
		
		if(calc(f, l) < calc(g, l) && calc(f, r) < calc(g, r)) {
			t[x].lin = g;
			return;
		}
		if(calc(f, l) >= calc(g, l)) {
			t[x].lin = g;
			tagd(ls(x), l, mid, f);
		}
		if(calc(f, r) >= calc(g, r)) {
			t[x].lin = g;
			tagd(rs(x), mid+1, r, f);
		}
	}
	void modify(int x, int l, int r, int seg) {
		if(t[x].l >= l && t[x].r <= r) {
			tagd(x, t[x].l, t[x].r, seg);
			return;
		}
		if(t[ls(x)].r >= l) modify(ls(x), l, r, seg);
		if(t[rs(x)].l <= r) modify(rs(x), l, r, seg);
	}
	void query(int x, int des, int &ans, db &num) {
//		printf("%.2f %d\n", calc(t[x].lin, des), t[x].lin);
		if(calc(t[x].lin, des) > num) {
			num = calc(t[x].lin, des);
			ans = t[x].lin;
		}
		else if(calc(t[x].lin, des) == num) {
			ans = min(ans, t[x].lin);
		}
		if(t[x].l == t[x].r) {
			return;
		}
		if(t[ls(x)].r >= des) query(ls(x), des, ans, num);
		else query(rs(x), des, ans, num);
	}
} T;

db xi[N], yi[N];
const ll M1 = 39989, M2 = 1000000000;
int main() {
//	freopen("P4097.in", "r", stdin);
//	freopen("P4097.out", "w", stdout);
	n=read();
	int i;
	int lastans=0;
	li[0] = (line){0, -1e9};
	int n1=0;
	int nn = 10;
	T.tree_pre(1, 1, 50000);
	for(i=1; i<=100000; i++) {
		xi[i] = 0;
		yi[i] = -1e9;
	}
	for(i=1; i<=n; i++) {
		int op=read();
		if(op == 0) {
			int k=read();
			k = (k+lastans-1)%39989 + 1;
			int ans=xi[k]; 
			db num=yi[k];
			T.query(1, k, ans, num);
			printf("%d\n", ans);
//			printf("%.2f\n", num);
			lastans = ans;
		}
		else {
			ll x0=read(), y0=read(), x1=read(), y1=read();
			x0 = (x0+lastans-1)%M1 + 1;
			y0 = (y0+lastans-1)%M2 + 1;
			x1 = (x1+lastans-1)%M1 + 1;
			y1 = (y1+lastans-1)%M2 + 1;
//			printf("%lld %lld %lld %lld\n", x0, y0, x1, y1);
			
			if(x0 > x1) swap(x0, x1), swap(y0, y1);
			n1++;
			if(x0 == x1) {
				if(max(y0, y1) > yi[x0]) yi[x0] = max(y0, y1), xi[x0] = n1; 
			}
			else {
				db k = (db)(y1-y0) / (db)(x1-x0);
				li[n1] = {k, y0-k*x0};
//				printf("%.2f %.2f\n", calc(n1, x0), calc(n1, x1));
				T.modify(1, x0, x1, n1);				
			}

		}
	}
	return 0;
}

标签:int,线段,李超,mid,x0,calc,lin
From: https://www.cnblogs.com/1n87/p/18359132

相关文章

  • unity中, 二维平面上,求从点A出发,沿着方向B,与线段C的交点
    代码说明:点A:起始点。方向B:一个方向向量,表示从点A出发的方向。线段C:由两个点C1和C2定义。1usingUnityEngine;23publicclassLineIntersection:MonoBehaviour4{5//返回从点A出发,沿着方向B,与线段C的交点。如果没有交点,则返回null6publicstati......
  • 可持久化线段————主席树(洛谷p3834)
    洛谷P3834可持久化线段树2问题描述:给定n各整数构成的序列,求指定区间[L,R]内的第k小值(求升序排序后从左往右数第k个整数的数值)输入:第一行输入两个整数n,m,分别代表序列长度n和对序列的m次查询;第二行输入n个整数,表示序列的n个整数;之后的m行,每行输入3个整数L,R,k,表示查询......
  • 线段树
    模板记得开4倍空间!!!Code#include<bits/stdc++.h>#definelllonglong#definepfprintf#definesfscanfusingnamespacestd;constintN=1e5+7;inttr[N*4];intf[N*4];inta[N];intn,q;intl,r,val;voidbuild(intu,intl,intr){ if(l==r){ tr[u]=a......
  • 【笔记】吉如一线段树
    【笔记】吉如一线段树吉如一论文(CQBZ内网,在PDF的103页1区间最值操作1.1区间取min(max),区间和当前应该修改值为\(x\);维护区间最大值\(mx\),最大值个数\(t\),严格次大值\(se\)。如果走到一个区间上,如果:\(x\gemx\),说明取min操作没用,直接return;\(mx>x>se\),打标......
  • 李超线段树板子
    点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=1e6+5;constintmod=1e9;constintp=39989;constdoubleeps=1e-9;intn,m;intans,ansid;inttcnt,rt;structlmy{ doublek,b;}a[N];structTree{ intls,rs,id;}tr[N];boolc......
  • 【笔记】传统势能线段树
    1引入传统线段树能够通过打标记实现区间修改的条件有两个:能够快速处理标记对区间询问结果的影响;能够快速实现标记的合并。有的区间修改不满足上面两个条件。但存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限。如果我们保证每暴力\(O(\logn)\)修改一次的时......
  • 线段树进阶 Part 1
    线段树是信息学竞赛最常见的数据结构。本篇笔记总结技巧和应用,不介绍基本线段树算法。1.常见技巧1.1信息设计用线段树解决问题,首先得考虑维护哪些信息。若不带修,任何满足结合律且封闭的信息(称为半群)都是可维护的。结合律一般都有,封闭性帮助我们设计信息。例如区间最大子段......
  • 「Day 7—离散化 & 树状数组 & 线段树」
    离散化定义离散化本质是一种哈希,是一种用来处理数据的方法。1.创建原数组的副本。2.将副本中的值从小到大排序。3.将排序好的副本去重。4.查找原数组的每一个元素在副本中的位置,位置即为排名,将其作为离散化后的值。B3694数列离散化代码#include<iostream>#include<algo......
  • 区间历史最值线段树记录
    Description维护一个线段树,使得可以实现区间加、区间chkmin、求区间最值、区间历史最值、区间最大值。Solution先不考虑区间chkmin和历史最值,可以直接对于每个线段树节点维护一个tag,每次addtag更新。加上区间历史最值后,先考虑对于单个线段树节点怎么更新。容易发现对于......
  • 线段树水题集合
    用的都是《算法竞赛》的码风哦洛谷P3870极简,开和关用10表示,然后区间修改,区间求和,裸题洛谷P2846这两道题一模一样#include<bits/stdc++.h>usingnamespacestd;constintN=100005;#definelllonglongllls(llp){returnp<<1;}llrs(llp){returnp<<1|1;}lln......