首页 > 其他分享 >P4097 【模板】李超线段树 / [HEOI2013] Segment

P4097 【模板】李超线段树 / [HEOI2013] Segment

时间:2024-07-02 22:12:24浏览次数:22  
标签:int 线段 李超 HEOI2013 P4097 y1 y0 x0 x1

P4097 【模板】李超线段树 / [HEOI2013] Segment

前言

李超线段树并不是一种新的线段树,而是对一类题维护最值的过程做了改进,使线段树仍然有不错的复杂度。

引入

简要题意

实现两种操作:

  1. 在区间 \([x_0,y_0]\) 上加入一条两端为 \((x_0,y_0)\),\((x_1,y_1)\) 的线段。
  2. 查询下标 \(k\) 上的纵坐标最大的线段的编号。

区别如区间覆盖,每个位置都插入不同的值,如果暴力,每次修改就要 \(O(n\log n)\) 的复杂度。李超线段树就是高效维护这类问题。

考虑线段树上每个节点 \(u\) 的 \(t_u\) 存下标 \(mid\) 上的取到最大值的线段的编号。先考虑修改操作。

现在插入一条线段 \(x\),那么和普通线段树一样,将 \([x_0,x_1]\) 拆成 \(\log n\) 个区间遍历。

假如此时遍历到的区间为 \([l,r]\),用新线段 \(x\) 在下标 \(k\) 上的纵坐标与线段 \(t_u\) 比较。如果新线段更大,那么说明至少有一半以上的区间的最大值编号都将是 \(x\),将 \(t_u\) 与 \(x\) 交换;反之则还是原线段 \(t_u\)。

运用标记永久化的思想,大于一半都是同一个编号的区间就不管了,直接把答案挂在 \(t_u\) 上不往下传了,另一半往下递归,因为线段 \(x\) 的贡献可能不止于此。比如:

image-20240702211944557

原本 \(x\) 现在为红线段,\(t_u\) 为蓝线段,比较了 \(mid\) 上的 \(x\) 和 \(t_u\) 之后,\(x\) 现在为蓝线段,\(t_u\) 为红线段。那么 \([l,mid]\) 不下传了,而此时 \(x\) 并不是没有用,因为在 \([mid,r]\) 之间又超过了红线段。所以继续往下递归 \([mid,r]\),维护 \(x\) 的贡献。

如何判断是否超过?只需要看两个端点上线段的纵坐标即可。

这样,修改操作就在 \(O(\log^2n)\) 的复杂度下完成了,因为每个区间都需要再递归 \(\log n\) 次维护。

查询操作简单,由于标记永久化,只要比较线段树上到 \([k,k]\) 的路径上的所有线段 \(t_u\) 在下标 \(k\) 上的纵坐标即可,答案一定在路径上。

总复杂度 \(O(n\log^2n)\)。

#include <iostream>
#define pii std::pair<int, int>
#define mk std::make_pair
#define fi first
#define se second
#define pb push_back

using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 1e5 + 10, mod1 = 39989, mod2 = 1e9;
int n, cnt;
double eps = 1e-9;
struct line {
	double k, b;
} a[N];
int t[N << 2];
int cmp(double x, double y) {
	if(x - y > eps) return 1;
	if(y - x > eps) return -1;
	return 0;
}
double calc(int id, int x) {
	return a[id].k * x + a[id].b; 
}
void add(int x0, int y0, int x1, int y1) {
	if(x0 == x1) {
		a[++cnt].k = 0, a[cnt].b = std::max(y0, y1);
	} else {
		a[++cnt].k = 1.0 * (y1 - y0) / (x1 - x0), a[cnt].b = y0 - a[cnt].k * x0;
	}
}
void mdf(int u, int l, int r, int x) {
	int mid = (l + r) >> 1;
	int bmid = cmp(calc(x, mid), calc(t[u], mid));
	if(bmid == 1 || (!bmid && x < t[u])) std::swap(x, t[u]);
	int bl = cmp(calc(x, l), calc(t[u], l)), br = cmp(calc(x, r), calc(t[u], r));
	if(bl == 1 || (!bl && x < t[u])) mdf(u << 1, l, mid, x);
	if(br == 1 || (!br && x < t[u])) mdf(u << 1 | 1, mid + 1, r, x);
}
void update(int u, int l, int r, int L, int R, int x) {
	if(L <= l && r <= R) {
		mdf(u, l, r, x);
		return;
	}
	int mid = (l + r) >> 1;
	if(L <= mid) update(u << 1, l, mid, L, R, x);
	if(R > mid) update(u << 1 | 1, mid + 1, r, L, R, x);
}
int mx(int x, int y, int z) {
	int ret = cmp(calc(x, z), calc(y, z));
	if(ret == 1) return x;
	else if(ret == -1) return y;
	else return (x < y ? x : y);
}
int qry(int u, int l, int r, int x) {
	if(l == r) return t[u];
	int mid = (l + r) >> 1;
	if(x <= mid) return mx(t[u], qry(u << 1, l, mid, x), x);
	else return mx(t[u], qry(u << 1 | 1, mid + 1, r, x), x); 
}
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
	std::cin >> n;

	int lstans = 0;
	while(n--) {
		int op, x0, y0, x1, y1, k;
		std::cin >> op;
		if(!op) {
			std::cin >> k;
			k = (k + lstans - 1) % mod1 + 1;
			std::cout << (lstans = qry(1, 1, mod1, k)) << "\n";
		} else {
			std::cin >> x0 >> y0 >> x1 >> y1;
			x0 = (x0 + lstans - 1) % mod1 + 1;
			x1 = (x1 + lstans - 1) % mod1 + 1;
			y0 = (y0 + lstans - 1) % mod2 + 1;
			y1 = (y1 + lstans - 1) % mod2 + 1;
			if(x0 > x1) std::swap(x0, x1), std::swap(y0, y1);
			add(x0, y0, x1, y1);
			update(1, 1, mod1, x0, x1, cnt);
		}
	}	

	return 0;
}

标签:int,线段,李超,HEOI2013,P4097,y1,y0,x0,x1
From: https://www.cnblogs.com/FireRaku/p/18280624

相关文章

  • C122 李超树合并+DP CF932F Escape Through Leaf
    视频链接:C122李超树合并+DPCF932FEscapeThroughLeaf_哔哩哔哩_bilibili   C65【模板】线段树合并P4556[Vani有约会]雨天的尾巴-董晓-博客园(cnblogs.com)CF932FEscapeThroughLeaf#include<iostream>#include<cstring>#include<algorithm>using......
  • C121 李超树+DP P4655 [CEOI2017] Building Bridges
    视频链接:C121李超树+DPP4655[CEOI2017]BuildingBridges_哔哩哔哩_bilibili   LuoguP4655[CEOI2017]BuildingBridges#include<iostream>#include<cstring>#include<algorithm>usingnamespacestd;#definelllonglong#definelsu<<......
  • C120 树剖+李超树 P4069 [SDOI2016] 游戏
    视频链接:C120树剖+李超树P4069[SDOI2016]游戏_哔哩哔哩_bilibili    D12LuoguP3384【模板】轻重链剖分/树链剖分-董晓-博客园(cnblogs.com) LuoguP4069[SDOI2016]游戏//树剖+李超树O(nlognlognlogn)#include<iostream>#include<cstring>#in......
  • 李超线段树
    李超线段树一般用来解决线段交集凸包问题:加入一个一次函数,定义域为\([l,r]\)给定\(k\),求定义域包含\(k\)的所有一次函数中,在\(x=k\)处取值最大的那个,如果有多个函数取值相同,选编号最小的看到区间修改,我们按照线段树解决区间问题的常见方法,给每个节点一个懒标记,每个节点......
  • [DS 小计] 李超线段树
    前言大家好啊,今天讲的是LCT(李超Tree)(bushi错了错了。这两不是一个东西。概念李超树能干什么。插入一条直线/线段单点查询当前点的峰值(最大最小均可)你会说:OI中有什么是和斜率相关的吗?有,那就是斜率优化。关于斜率优化可以看这个。你会说:你说的对,静态的dp......
  • 斜率优化&李超树
    斜率优化dp1.写出dp方程,并让其变为线性其中可能需要对性质的重要分析(难点)式子一般长这个样$$f(i)=min/max(h(j)+a(i)b(j)+g(i))$$2.模板化的,分离变量具体的,先忽略\(min/max\)(其实相当于选择一个最优的\(j_0\))$$f(i)=h(j_0)+a(i)b(j_0)+g(i)$$把只与\(j_0\)相关的......
  • 李超线段树
    模板题动态添加线段,求某个\(x\)对应的\(y\)最大是多少,且对应哪条直线。因为\(x\)比较小,考虑在\(x\)轴上建立线段树。把每个线段写成\(y=kx+b\)的解析式形式并求出它的定义域\([l,r]\),每条线段就可以看作是一个应用在\([l,r]\)上的区间修改。每次查询就是单点查询。......
  • 李超线段树学习笔记
    前言如有错误,欢迎各位大佬指出。GM说学了斜率和线段树就可以尝试。前置芝士:斜率线段树1.什么是李超线段树?李超线段树主要解决平面坐标系内有关直线的问题,李超线段树是一种特殊的线段树。这里给出一个引例P4097[HEOI2013]Segment。题目大意及要维护两个操作:给......
  • CF932F Escape Through Leaf【DP,李超线段树】
    有一颗\(n\)个节点的树,根节点为\(1\)。每个节点有两个权值,第\(i\)个节点的权值为\(a_i,b_i\)。你可以从一个节点跳到它的子树内任意一个节点上。从节点\(x\)跳到节点\(y\)一次的花费为\(a_x\timesb_y\)。跳跃多次走过一条路径的总费用为每次跳跃的费用之和。求每个节......
  • 这就是我们的李超线段树啊,你们有没有这样的李超线段树啊?
    沟槽的公式,真是公公又式式啊。考虑一个线段树节点维护一个线段(但一条线段可以被多个线段树节点维护),需要保证该节点被线段完全覆盖。每次添加一个线段的时候:如果当前节点没有被这个线段完全覆盖,那么直接递归左右儿子修改。如果当前节点的线段比新线段严格劣(也就是对于每一......