简介
一般来说,我们处理某些可以离线的问题,我们会将询问离线,然后将修改挂在左端点或右端点,然后从左往右扫描这些修改,并处理询问,数据结构记录的一般是下标 \(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] フードコート
题意: 维护一个队列的序列,三个操作:
-
将一个区间的所有队列末尾加入 \(K\) 个 \(C\)。
-
将一个区间的所有队列弹出队头的 \(K\) 个数,不够就全部弹出。
-
查询某个队列第 \(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