洛谷P8868 [NOIP2022] 比赛
离线所有询问(按右端点排序),然后枚举右端点 \(r\)。
记 \(X_l\) 为 \(a\) 在区间 \([l, r]\) 中的最大值,\(Y_l\) 为 \(b\) 在区间 \([l, r]\) 中的最大值。
在枚举的过程中,对每个 \(l\), \(f(i) \leftarrow X_lY_l\),则对于一个右端点为 \(r\) 的询问 \([x, r]\),其答案为 \(\sum\limits_{i = l}^r f(i)\)。
这样的时间复杂度是 \(O(n^2 + nq)\),可以拿到 \(20\) 分。
考虑加速 \(X, Y, f\) 的计算,也就是要写一种数据结构,能够实现:
- \(X, Y, f\) 的区间修改。
- \(f\) 的区间和查询。
自然想到线段树。
\(X, Y\) 都会被修改,因此维护 \(sumf, sumX, sumY, sumXY\) 四个值和 \(cx, cy, tx, ty,txy, add\) 六个懒标记。
四个值的意义就是字面意义。
\(cx\) 表示当前区间的 \(X\) 值会被修改为 \(cx\),同理 \(cy\) 表示当前区间的 \(Y\) 值会被修改为 \(cy\)。
\(tx\) 表示累加进 \(sumf\) 中的 \(x\) 的倍数,\(ty\) 表示累加进 \(sumf\) 中的 \(y\) 的倍数,\(txy\) 表示累加进 \(sumf\) 中的 \(xy\) 的倍数,\(add\) 表示要累加进 \(sumf\) 的值。
pushup 比较常规,四个值分别为左右子之和即可。难点在 pushdown。
以下放左子(\(pos\) 下放至 \(lson\))为例,先处理贡献再覆盖,即:
- 若 \(cx_{lson}\) 和 \(cy_{lson}\) 都有值,则将 \(cx_{lson}\) 和 \(cy_{lson}\) 的贡献都直接归到 \(add_{lson}\) 中,即:
- 若仅 \(cx_{lson}\) 有值,则将 \(cx_{lson}\) 的贡献归到 \(add_{lson}\) 和 \(ty_{lson}\) 中,即:
- 相似地,若仅 \(cy_{lson}\) 有值,则将 \(cy_{lson}\) 的贡献归到 \(add_{lson}\) 和 \(tx_{lson}\) 中,即:
- 否则,也就是 \(cx_{lson}\) 和 \(cy_{lson}\) 都没有值,直接累加即可。
然后再根据 \(cx_{pos}\) 和 \(cy_{pos}\) 的值的有无决定是否覆盖 \(cx_{lson}, cy_{lson}\)。
至此,标记就更新完了,接下来考虑值的更新。
然后再根据 \(cx_{pos}\) 和 \(cy_{pos}\) 的值的有无决定是否更新 \(sumX, sumY, sumXY\)。
最后就是要明确每次我们要更新哪些区间,查询哪个区间。
通过单调栈预处理出每个 \(a_i\) 左侧第一个大于等于 \(a_i\) 的元素的下标 \(la_i\) 和每个 \(b_i\) 左侧第一个大于等于 \(b_i\) 的元素的下标 \(lb_i\)。易知 \(a_i\) 对 \([1, la_i]\) 的答案没有影响,同理,\(b_i\) 对 \([1, lb_i]\) 的答案没有影响。
因此,每次更新 \([la_i + 1, r]\) 的 \(X\),\([lb_i + 1, r]\) 的 \(Y\) 和 \([1, r]\) 的 \(f\),然后对每个右端点为 \(r\) 的询问 \([x, r]\),查询 \([x, r]\) 的区间 \(f\) 和即可。
时间复杂度 \(O(n \log n)\),快得飞起。
代码:
#include <bits/stdc++.h>
#define MAXN 250100
#define lson pos << 1
#define rson pos << 1 | 1
using namespace std;
typedef unsigned long long ull;
int n, m;
ull a[MAXN], b[MAXN], ans[MAXN];
int top, stk[MAXN], la[MAXN], lb[MAXN];
struct Tag {
ull cx, cy, tx, ty, txy, add;
void operator*=(const Tag &rhs) { // 更新标记
if (cx && cy) {
add += cx * rhs.tx + cy * rhs.ty + cx * cy * rhs.txy + rhs.add;
} else if (cx) {
add += cx * rhs.tx + rhs.add;
ty += cx * rhs.txy + rhs.ty;
} else if (cy) {
add += cy * rhs.ty + rhs.add;
tx += cy * rhs.txy + rhs.tx;
} else {
tx += rhs.tx; ty += rhs.ty; txy += rhs.txy; add += rhs.add;
}
if (rhs.cx) cx = rhs.cx;
if (rhs.cy) cy = rhs.cy;
}
bool exist() {
return cx || cy || tx || ty || txy || add; // 判断整个 Tag 是否有值
}
};
struct Seg {
ull sx[MAXN << 2], sy[MAXN << 2], sxy[MAXN << 2], f[MAXN << 2];
Tag lazy[MAXN << 2];
void fix(int pos, int len, Tag t) { // 更新值
f[pos] += sx[pos] * t.tx + sy[pos] * t.ty + sxy[pos] * t.txy + t.add * len;
if (t.cx && t.cy) {
sx[pos] = t.cx * len; sy[pos] = t.cy * len;
sxy[pos] = t.cx * t.cy * len;
} else if (t.cx) {
sx[pos] = t.cx * len;
sxy[pos] = t.cx * sy[pos];
} else if (t.cy) {
sy[pos] = t.cy * len;
sxy[pos] = t.cy * sx[pos];
}
}
void pushup(int pos) {
sx[pos] = sx[lson] + sx[rson];
sy[pos] = sy[lson] + sy[rson];
sxy[pos] = sxy[lson] + sxy[rson];
f[pos] = f[lson] + f[rson];
}
void pushdown(int pos, int len) {
if (!lazy[pos].exist()) return;
lazy[lson] *= lazy[pos]; lazy[rson] *= lazy[pos];
fix(lson, len - (len >> 1), lazy[pos]); fix(rson, len >> 1, lazy[pos]);
lazy[pos] = {0, 0, 0, 0, 0, 0};
}
void upd(int pos, int l, int r, int x, int y, Tag t) {
if (x <= l && r <= y) {
// 更新标记和值
// 与 pushdown 相同,只不过这时候是将 t 下放到 pos。
lazy[pos] *= t;
fix(pos, r - l + 1, t);
return;
}
pushdown(pos, r - l + 1);
int mid = (l + r) >> 1;
if (x <= mid) upd(lson, l, mid, x, y, t);
if (y > mid) upd(rson, mid + 1, r, x, y, t);
pushup(pos);
}
ull query(int pos, int l, int r, int x, int y) {
if (x <= l && r <= y) return f[pos];
pushdown(pos, r - l + 1);
int mid = (l + r) >> 1;
ull res = 0;
if (x <= mid) res += query(lson, l, mid, x, y);
if (y > mid) res += query(rson, mid + 1, r, x, y);
return res;
}
} T;
struct Node {
int l, r, id;
bool operator<(const Node &rhs) const {
return r < rhs.r;
}
} ask[MAXN];
template<typename _T>
void read(_T &_x) {
_x = 0;
_T _f = 1;
char _ch = getchar();
while (_ch < '0' || '9' < _ch) {
if (_ch == '-') _f = -1;
_ch = getchar();
}
while ('0' <= _ch && _ch <= '9') {
_x = (_x << 3) + (_x << 1) + (_ch & 15);
_ch = getchar();
}
_x *= _f;
}
template<typename _T>
void write(_T _x) {
if (_x < 0) {
putchar('-');
_x = -_x;
}
if (_x > 9) write(_x / 10);
putchar('0' + _x % 10);
}
int main() {
read(n); read(n);
for (int i = 1; i <= n; i++) read(a[i]);
for (int i = 1; i <= n; i++) read(b[i]);
read(m);
// 单调栈预处理 la[] 和 lb[],从右向左处理
// 能将 a[j] 弹出的 a[i] 即为 a[j] 左侧第一个大于等于 a[j] 的元素
for (int i = 1; i <= m; i++) {
ask[i].id = i;
read(ask[i].l); read(ask[i].r);
}
for (int i = n; i; i--) {
while (top && a[i] >= a[stk[top]]) la[stk[top--]] = i;
stk[++top] = i;
}
top = 0;
for (int i = n; i; i--) {
while (top && b[i] >= b[stk[top]]) lb[stk[top--]] = i;
stk[++top] = i;
}
sort(ask + 1, ask + m + 1);
int cur = 1;
for (int i = 1; i <= n; i++) {
// 直接以下放 Tag 的形式更新,省得写 3 个 upd 函数
T.upd(1, 1, n, la[i] + 1, i, Tag{a[i], 0, 0, 0, 0, 0});
T.upd(1, 1, n, lb[i] + 1, i, Tag{0, b[i], 0, 0, 0, 0});
T.upd(1, 1, n, 1, i, Tag{0, 0, 0, 0, 1, 0});
// 统计答案
while (cur <= m && ask[cur].r == i) {
ans[ask[cur].id] = T.query(1, 1, n, ask[cur].l, i);
cur++;
}
if (cur > m) break;
}
for (int i = 1; i <= m; i++) {
write(ans[i]); putchar('\n');
}
return 0;
}