换维扫描线

简介

一般来说,我们处理某些可以离线的问题,我们会将询问离线,然后将修改挂在左端点或右端点,然后从左往右扫描这些修改,并处理询问,数据结构记录的一般是下标 \(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\),分块处理即可。

posted @ 2024-03-18 19:59  rlc202204  阅读(14)  评论(0编辑  收藏  举报