porse114514
2025-01-05 19:03:22
lxl 的课属实让我受益匪浅,这篇博客就来谈一谈他自创的算法:插入-标记-查找。
这是一个离线算法,用到了扫描线思想和数据结构,它可以秒掉这样一类问题:
这个算法的优点是代码简单,面对不同的题目时核心代码(除了数据结构部分)都差不多,赛场上容易写出来。
但是这个算法也有缺点,首先,它不能解决在线问题,而且他虽然能秒题,但不一定是最优解(有时会多一个 log),导致容易被他的发明者卡常。
首先我们知道几乎所有数据结构都可以全局做某个操作,那么我们考虑对于所有
对于一组询问,我们先对左端点排序,然后开始从左往右扫描线,对于扫到的每个位置
如果你觉得上面的东西有些难懂,不妨看看下面的图解。
我们设
然后扫描线扫到
在看图解的时候,想必大家发现了好多细节问题,接下来一一阐明:
例题:Nanno and Bug
给定一个常数
这题就是这个算法的模板加一个有交合并(详见代码)平衡树,思路不多赘述。
这道题虽是模板,但被卡常,如果你 Time limit exceeded on test
#include <bits/stdc++.h>
using namespace std;
#define int long long
constexpr int maxn = 1000010;
struct question {
int l, r, i;
} ql[maxn], qr[maxn]; // ql: 对 l 排序,qr:对 r 排序
int n, m, p, a[maxn], ans[maxn], it[maxn]; // it 为指针,ans 为答案
struct FHQnode {
int v, ran, ls, rs, tag, fa;
} ;
struct FHQtreap {
FHQnode node[maxn];
int cnt, root;
void pushup(int u) { // 前文的细节 2,维护 fa
node[u].fa = 0;
if (node[u].ls) node[node[u].ls].fa = u;
if (node[u].rs) node[node[u].rs].fa = u;
return ;
}
void pushdown(int u) {
node[node[u].ls].v += node[u].tag;
node[node[u].ls].tag += node[u].tag;
node[node[u].rs].v += node[u].tag;
node[node[u].rs].tag += node[u].tag;
node[u].tag = 0;
return ;
}
void split(int u, int &l, int &r, int x) {
if (!u) {
l = r = 0;
return ;
}
pushdown(u);
if (node[u].v <= x) {
l = u;
split(node[u].rs, node[u].rs, r, x);
node[u].fa = 0;
} else {
r = u;
split(node[u].ls, l, node[u].ls, x);
}
pushup(u);
return ;
}
int merge(int l, int r) {
if (!l || !r) return l + r;
if (node[l].ran <= node[r].ran) {
pushdown(l);
node[l].rs = merge(node[l].rs, r);
pushup(l);
return l;
} else {
pushdown(r);
node[r].ls = merge(l, node[r].ls);
pushup(r);
return r;
}
}
int Merge(int l, int r) { // 与这个算法关系很深的技巧(用的非常多): 平衡树有交和并,详见 https://rainlycoris.github.io/posts/P5494/(by @DaydreamWarrior in luogu)
if (!l || !r) return l + r;
int L, R;
if (node[l].ran <= node[r].ran) {
split(r, L, R, node[l].v);
pushdown(l);
node[l].ls = Merge(node[l].ls, L);
node[l].rs = Merge(node[l].rs, R);
pushup(l);
return l;
} else {
split(l, L, R, node[r].v);
pushdown(r);
node[r].ls = Merge(node[r].ls, L);
node[r].rs = Merge(node[r].rs, R);
pushup(r);
return r;
}
}
void ins(int i) {
node[++cnt] = {0, rand(), 0, 0, 0, 0};
it[i] = cnt;
int l, r;
split(root, l, r, 0);
root = merge(merge(l, cnt), r);
return ;
}
void update(int k) {
node[root].tag += k;
node[root].v += k;
int l, r;
split(root, l, r, p - 1);
node[r].tag -= p;
node[r].v -= p;
root = Merge(l, r);
return ;
}
void pushalldown(int u) { // 前文的细节 1
if (node[u].fa) pushalldown(node[u].fa);
pushdown(u);
return ;
}
} t;
signed main() {
srand((unsigned)time(NULL));
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> m >> p;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= m; i++) {
ql[i].i = i;
cin >> ql[i].l >> ql[i].r;
}
for (int i = 1; i <= m; i++) {
qr[i] = ql[i];
}
sort(ql + 1, ql + 1 + m, [](question a, question b){return a.l < b.l;});
sort(qr + 1, qr + 1 + m, [](question a, question b){return a.r < b.r;}); // 排序,为后面的双指针做准备
for (int i = 1, j = 1, k = 1; i <= n; i++) {
while (ql[j].l == i) { // 双指针
t.ins(ql[j].i);
j++;
}
t.update(a[i]);
while (qr[k].r == i) { // 双指针
t.pushalldown(it[qr[k].i]);
ans[qr[k].i] = t.node[it[qr[k].i]].v;
k++;
}
}
for (int i = 1; i <= m; i++) {
cout << ans[i] << '\n';
}
return 0;
}