首页 > 其他分享 >换维扫描线

换维扫描线

时间:2024-03-18 20:13:08浏览次数:30  
标签:val int rx long 换维 ls 扫描线 lx

简介

一般来说,我们处理某些可以离线的问题,我们会将询问离线,然后将修改挂在左端点或右端点,然后从左往右扫描这些修改,并处理询问,数据结构记录的一般是下标 \(i\) 到当前走到的地方的一些信息。而换维扫描线则采取了截然相反的措施:我们将区间修改转化成差分,然后从左往右扫描序列,线段树维护的是时间轴的信息,每次对于一个元素单独查询。

当查询都是单点,且修改操作可以差分的时候,我们可以使用换维扫描线这种做法。

例题

CF1906F Maximize The Value

题意: 一个序列 \(a\) 和一个操作序列,操作是区间加,若干此询问,每次询问给定区间和一个位置,求在操作序列的这个区间选一个操作序列的子段,使得执行完这些操作后这个位置上的数最大,求最大值,询问之间互相独立。

思路:

将操作转成差分,然后我们维护操作序列,扫描序列,每当到一个点的时候,第 \(i\) 个位置记录的是第 \(i\) 歌操作的贡献,询问就变成了区间最大子段和。这样就可以很轻松的解决这个问题。

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;

//线段树求最大子段和,不允许空 
struct Value {
	long long ans, sum, pmx, smx, len;
	Value () {
		ans = sum = pmx = smx = len = 0ll;
	}
}val[N << 2];
Value operator+(Value x, Value y) {
	if (x.len == 0)
		return y;
	if (y.len == 0)
		return x;
	Value ans = Value();
	ans.len = x.len + y.len;
	ans.sum = x.sum + y.sum;
	ans.pmx = max(x.pmx, x.sum + y.pmx);
	ans.smx = max(y.smx, y.sum + x.smx);
	ans.ans = max(x.smx + y.pmx, max(x.ans, y.ans));
	return ans;
}

struct SegTree {
	#define ls (x << 1)
	#define rs (x << 1 | 1)
	#define mid ((lx + rx) >> 1)
	void pushup(int x) {
		val[x] = val[ls] + val[rs];
	}
	void build(int x, int lx, int rx) {
		if (lx + 1 == rx) {
			val[x].len = 1;
			return;
		}
		build(ls, lx, mid), build(rs, mid, rx);
		pushup(x); 
	}
	void mdf(int x, int lx, int rx, int pos, int v) {
		if (lx + 1 == rx) {
			val[x].sum += v;
			val[x].pmx = val[x].smx = val[x].ans = val[x].sum;
			return;
		}
		(pos < mid) ? mdf(ls, lx, mid, pos, v) : mdf(rs, mid, rx, pos, v);
		pushup(x);
	}
	Value qry(int x, int lx, int rx, int l, int r) {
		if (rx <= l || r <= lx)
			return Value();
		if (l <= lx && rx <= r)
			return val[x];
		return qry(ls, lx, mid, l, r) + qry(rs, mid, rx, l, r);
	}
	SegTree () {}
	#undef ls
	#undef rs
	#undef mid 
} st;

int n, m, Q;
struct Pnt {
	int x, y;
	Pnt (int _x = 0, int _y = 0) :
		x(_x), y(_y) {}
};
vector<Pnt> c[N];
vector<pair<Pnt, int> > q[N];
long long ans[N] = {0};

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1, l, r, x; i <= m; i++) {
		scanf("%d%d%d", &l, &r, &x);
		c[l].push_back(Pnt(i, x));
		c[r + 1].push_back(Pnt(i, -x));
	}
	scanf("%d", &Q);
	for (int i = 1, k, l, r; i <= Q; i++) {
		scanf("%d%d%d", &k, &l, &r);
		q[k].push_back(make_pair(Pnt(l, r), i));
	}
	st.build(1, 1, m + 1);
	for (int i = 1; i <= n; i++) {
		for (auto j: c[i])
			st.mdf(1, 1, m + 1, j.x, j.y);
		for (auto j: q[i])
			ans[j.second] = st.qry(1, 1, m + 1, j.first.x, j.first.y + 1).ans;
	}
	for (int i = 1; i <= Q; i++)
		printf("%lld\n", ans[i]);
	return 0;
}
P7560 [JOISC 2021 Day1] フードコート

题意: 维护一个队列的序列,三个操作:

  1. 将一个区间的所有队列末尾加入 \(K\) 个 \(C\)。

  2. 将一个区间的所有队列弹出队头的 \(K\) 个数,不够就全部弹出。

  3. 查询某个队列第 \(B\) 个位置的值。

思路:

先观察,由于有可能有些队列会被不断清空,不好维护。

我们发现,由于是弹出队头元素,这说明当前队列中所有数一定是所有加入的数的一个后缀。

所以我们只用知道总共加入了多少元素以及现在还剩多少元素,我们就知道每次查询的是所有加入的数中的第几个。

总共加入多少元素可以用树状数组维护区间加,单点查,而现在还剩多少元素相当于区间取 \(max\) 和区间加,单点查询,可以用一个二元的 \(tag\) 来维护。

具体的,设 \((a, b)\) 表示 \(x \to \max(x + a, b)\),两个标记的符合就是 \((a_1, b_1) + (a_2, b_2) = (a_1 + a_2, \max(b_1 + a_2, b_2))\)。

由于是单点查询,转化成换维扫描线,我们只用查询前缀中第一个前缀和大于某个值的位置,这个可以用一个简单的线段树上二分解决。

所以我们就解决了这个问题。

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 250005;
const long long inf = 0x3f3f3f3f3f3f3f3fll;
//第一个线段树:区间加区间求 mx,单点查询
struct LazyTag {
	long long a, m;
	LazyTag () {
		a = 0ll, m = ~inf;
	}
	LazyTag (long long A, long long M) {
		a = A, m = M;
	}
} tag[N << 4];
LazyTag operator*(LazyTag x, LazyTag y) {
	return LazyTag(x.a + y.a, max(x.m + y.a, y.m));
}
long long operator*(long long x, LazyTag y) {
	return max(x + y.a, y.m);
}
struct SegTree1 {
	#define ls (x << 1)
	#define rs (x << 1 | 1)	
	#define mid ((lx + rx) >> 1)
	void pushdown(int x) {
		tag[ls] = tag[ls] * tag[x];
		tag[rs] = tag[rs] * tag[x];
		tag[x] = LazyTag();
	}
	void upd(int x, int lx, int rx, int l, int r, LazyTag v) {
		if (rx <= l || r <= lx)
			return;
		if (l <= lx && rx <= r) {
			tag[x] = tag[x] * v;
			return;
		}
		pushdown(x);
		upd(ls, lx, mid, l, r, v), upd(rs, mid, rx, l, r, v);
	}
	long long qry(int x, int lx, int rx, int pos) {
		if (lx + 1 == rx)
			return max(tag[x].a, tag[x].m);
		pushdown(x);
		return (pos < mid) ? qry(ls, lx, mid, pos) : qry(rs, mid, rx, pos);
	}
	SegTree1 () {}
	#undef ls
	#undef rs
	#undef mid
} st1;

//实现单点加,前缀二分第一个大于等于某个数的前缀和
long long val[N << 2] = {0};
struct SegTree2 {
	#define ls (x << 1)
	#define rs (x << 1 | 1)
	#define mid ((lx + rx) >> 1)
	void pushup(int x) {
		val[x] = val[ls] + val[rs];
	}
	void mdf(int x, int lx, int rx, int pos, int v) {
		if (lx + 1 == rx) {
			val[x] += v;
			return;
		}
		(pos < mid) ? mdf(ls, lx, mid, pos, v) : mdf(rs, mid, rx, pos, v);
		pushup(x); 
	}
	long long qry(int x, int lx, int rx, long long k) {
		if (val[x] < k)
			return 0ll;
		if (lx + 1 == rx)
			return lx;
		return (val[ls] >= k) ? qry(ls, lx, mid, k) : qry(rs, mid, rx, k - val[ls]);
	}
	SegTree2 () {}
	#undef ls
	#undef rs
	#undef mid
} st2; 

//第三个树状数组,查分实现正常的区间加和单点求值 
long long t[N] = {0};
struct BIT {
	int n;
	BIT () {}
	BIT (int _n) {
		n = _n;
	}
	int lowbit(int x) {
		return x & -x;
	}
	void mdf(int x, int v) {
		while (x <= n)
			t[x] += v, x += lowbit(x);
	}
	long long qry(int x) {
		long long ans = 0ll;
		while (x > 0)
		 	ans += t[x], x -= lowbit(x);
		return ans;
	}
} ft;


int n, m, Q;
int c[N] = {0};//表示这次操作加入的是哪个 
struct Pnt {
	long long x, y;
	Pnt (long long _x = 0ll, long long _y = 0ll) :
		x(_x), y(_y) {}
};
vector<Pnt> q[N];//查询 
vector<Pnt> f[N];

int ans[N] = {0};

int main() {
	cin >> n >> m >> Q;
	ft = BIT(n);
	for (int i = 1, op; i <= Q; i++) {
		ans[i] = -1;
		cin >> op;
		if (op == 1) {
			int l, r, k;
			cin >> l >> r >> c[i] >> k;
			ft.mdf(l, k), ft.mdf(r + 1, -k);
			st1.upd(1, 1, n + 1, l, r + 1, LazyTag(k, ~inf));
			f[l].push_back(Pnt(i, k));
			f[r + 1].push_back(Pnt(i, -k));
		}
		else if (op == 2) {
			int l, r, k;
			cin >> l >> r >> k;
			st1.upd(1, 1, n + 1, l, r + 1, LazyTag(-k, 0ll));
		}
		else {
			int a;
			long long b;
			cin >> a >> b;
		//	cout << "now " << a << " has " << st1.qry(1, 1, n + 1, a) << " remain " << " total is  " << ft.qry(a) << endl;
			q[a].push_back(Pnt(b + ft.qry(a) - st1.qry(1, 1, n + 1, a), i));
		}
	}	
	for (int i = 1; i <= n; i++) {
		for (auto j: f[i])
			st2.mdf(1, 1, Q + 1, j.x, j.y);
		for (auto j: q[i]) {
		//	cout << i << " query for " << j.x << " " << j.y << endl;
			ans[j.y] = st2.qry(1, 1, Q + 1, j.x);
			ans[j.y] = (ans[j.y] > j.y ? 0 : c[ans[j.y]]);
		}
	}
	for (int i = 1; i <= Q; i++)
		if (ans[i] != -1)
			cout << ans[i] << endl;
	return 0;
}
P3863 序列

题意: 两种操作,每种操作耗费一秒:区间加,查询某个数在过去多少个时刻大于 \(y\)。

思路:

换维扫描线,转化成求多少前缀小于 \(y\),分块处理即可。

标签:val,int,rx,long,换维,ls,扫描线,lx
From: https://www.cnblogs.com/rlc202204/p/18081291

相关文章

  • 计算几何——扫描线 学习笔记
    计算几何——扫描线学习笔记你会发现我的笔记的顺序和很多扫描线的讲解是反着来的。其实是和我老师给的课件完全是逆序(谁帮我算一下逆序对啊喵)。前言一开始以为扫描线就是用来求二维几何图像的信息的。但是其实这个并不准确。个人认为,扫描线其实是一个思想,就像动态规划一样......
  • 扫描线学习笔记
    1.引入扫描线多用于图形上,是一条线在图形上扫来扫去,它一般被用来解决图形面积,周长,以及二维数点等问题。2.扫描线求面积并如下图:我们模拟一条扫描线,使它从下往上扫过整个平面,这条扫描线会在遇到横向线段的时候停下来更新一些东西。那么整个图形就可以找出四条线段,如图:更新的......
  • 【学习笔记】扫描线
    bilibili:BV1Mm411D7kr讲了一下。模板代码:面积并:#include<cstring>#include<iostream>#include<algorithm>#defineintlonglongusingnamespacestd;namespaceIO{template<typenameT>Tread(Tx){Tsum=0,opt=1......
  • 扫描线
    扫描线的精髓是通过离线、引入时间维,把静态询问降低一维、变成动态问题。一般是先把若干询问通过可减性变成前缀询问,再离线、从左到右排序,从左到右一个一个地一边加入元素,一边回答询问。以下是例题+一句话题解。(虽然可能不是真的一句话)平面最值二维平面上\(n(\le10^5)\)个......
  • 线段树维护扫描线
    你需要实现一种数据结构支持以下操作:区间加减1保证加减区间一一对应,且先加后减,序列中永远不出现负数。查询完整序列中0的个数#include<cstdio>#include<algorithm>constintmaxn=1e5+10;longlongx[maxn*2];structsegmentTree{ structnode { i......
  • 扫描线
    AcWing247.亚特兰蒂斯题意:给定若干个矩形(长宽均平行于坐标轴),求它们的面积并(矩形的顶点坐标可以是实数)。本题是扫描线算法的模板题。扫描线,顾名思义,就是按照一条线扫过去,对于本题扫描线-OIWiki(oi-wiki.org)如上图,这就是扫描线的过程。发现按照线扫描的话,每次我们只需......
  • 【模板】扫描线
    【模板】扫描线题目描述求$n$个四边平行于坐标轴的矩形的面积并。输入格式第一行一个正整数$n$。接下来$n$行每行四个非负整数$x_1,y_1,x_2,y_2$,表示一个矩形的四个端点坐标为$(x_1,y_1),(x_1,y_2),(x_2,y_2),(x_2,y_1)$。输出格式一行一个正整数,表示$n$个......
  • 浅谈扫描线
    Preface本文部分题目摘自lxl的数据结构系列课件由于工程量巨大,难免会出现错字、漏字的情况,如果影响到了您的阅读体验,还请私信我,我会在第一时间修复。本文建议大家有一定的数据结构基础后再来阅读/heart。个人感觉扫描线不是难点,难点在于怎么抽象模型。首先需要明白一个东......
  • 扫描线
    假设有一条扫描线从一个图形的下方扫向上方(或者左方扫到右方),那么通过分析扫描线被图形截得的线段就能获得所要的结果。该过程可以用线段树进行加速。P5490【模板】扫描线扫描线可以处理两类问题一类是面积并一类是周长并#include<iostream>#include<algorithm>usingnames......
  • Auguryの扫描线分享
    Auguryの扫描线分享扫描线是啥有时候答案是不好计算的,但是答案可以拆分成多个段分别计算,且段与段之间可以快速转换,我们就可以用扫描线解决。或者说,一个二维问题,我们可以用扫描线变成一维。前置芝士线段树、值域线段树、树状数组(胡扬好闪,拜谢胡扬)离散化现在我们有一堆数,你......