题目描述
Rip Van Winkle\texttt{Rip Van Winkle}Rip Van Winkle 厌倦了除编程之外的一切。有一天他遇到了一个问题,需要对数组 data[]\texttt{data[]}data[] 执行三种更新操作(A、B、C)和一种查询操作 S。初始时,数组的所有元素都为 000。虽然 Rip Van Winkle\texttt{Rip Van Winkle}Rip Van Winkle 要睡 202020 年,他的代码也非常慢,但你需要以高效的方式执行相同的更新操作,并输出查询操作 S 的结果。
给定的慢速代码如下:
long long data[250001];
void A(int st, int nd) {
for(int i = st; i <= nd; i++) data[i] = data[i] + (i - st + 1);
}
void B(int st, int nd) {
for(int i = st; i <= nd; i++) data[i] = data[i] + (nd - i + 1);
}
void C(int st, int nd, int x) {
for(int i = st; i <= nd; i++) data[i] = x;
}
long long S(int st, int nd) {
long long res = 0;
for(int i = st; i <= nd; i++) res += data[i];
return res;
}
输入格式
第一行包含整数 TTT(≤4×105\leq 4 \times 10^5≤4×105),表示操作数量。接下来的 TTT 行,每行以一个字符开头(A\texttt{A}A、B\texttt{B}B、C\texttt{C}C 或 S\texttt{S}S),表示操作类型。字符 A\texttt{A}A、B\texttt{B}B 或 S\texttt{S}S 后跟两个整数 ststst 和 ndndnd;字符 C\texttt{C}C 后跟三个整数 ststst、ndndnd 和 xxx。假设 1≤st≤nd≤2500001 \leq st \leq nd \leq 2500001≤st≤nd≤250000,−105≤x≤105-10^5 \leq x \leq 10^5−105≤x≤105。
输出格式
对于每个以字符 S\texttt{S}S 开头的行,输出 S(st,nd)S(st, nd)S(st,nd) 的结果。
题目分析
直接模拟的问题
如果直接按照题目给出的代码实现,时间复杂度会非常高:
- 数组大小:250,000250,000250,000
- 操作次数:最多 400,000400,000400,000
- 每次操作可能覆盖整个数组
最坏情况下,总时间复杂度为 O(T×N)O(T \times N)O(T×N),即 400,000×250,000=1011400,000 \times 250,000 = 10^{11}400,000×250,000=1011 次操作,这显然会超时。
操作分析
我们需要处理四种操作:
-
操作
A:在区间 [st,nd][st, nd][st,nd] 上,对每个位置 iii 加上 (i−st+1)(i - st + 1)(i−st+1)- 这是一个从 111 开始递增的等差数列
-
操作
B:在区间 [st,nd][st, nd][st,nd] 上,对每个位置 iii 加上 (nd−i+1)(nd - i + 1)(nd−i+1)- 这是一个从大到小递减的等差数列
-
操作
C:在区间 [st,nd][st, nd][st,nd] 上,将所有元素设置为 xxx- 这是一个区间覆盖操作
-
操作
S:查询区间 [st,nd][st, nd][st,nd] 的和
数学建模
对于等差数列操作,我们可以用线性函数来表示:
-
操作
A:对于位置 iii,加上 i×1+(−st+1)i \times 1 + (-st + 1)i×1+(−st+1)- 即 k=1k = 1k=1, b=−st+1b = -st + 1b=−st+1
-
操作
B:对于位置 iii,加上 i×(−1)+(nd+1)i \times (-1) + (nd + 1)i×(−1)+(nd+1)- 即 k=−1k = -1k=−1, b=nd+1b = nd + 1b=nd+1
等差数列在区间 [l,r][l, r][l,r] 上的和可以用数学公式直接计算:
∑i=lr(k⋅i+b)=k⋅(l+r)⋅(r−l+1)2+b⋅(r−l+1)\sum_{i=l}^{r} (k \cdot i + b) = k \cdot \frac{(l + r) \cdot (r - l + 1)}{2} + b \cdot (r - l + 1)i=l∑r(k⋅i+b)=k⋅2(l+r)⋅(r−l+1)+b⋅(r−l+1)
这个公式让我们可以在 O(1)O(1)O(1) 时间内计算任意区间上等差数列的和。
解题思路
数据结构选择
由于需要高效处理区间更新和区间查询,我们选择线段树作为基础数据结构。每个线段树节点需要维护:
- sum\texttt{sum}sum:区间和
- cover\texttt{cover}cover:覆盖标记(特殊值表示未覆盖)
- kkk, bbb:线性标记,表示需要加上的函数 k⋅i+bk \cdot i + bk⋅i+b
- len\texttt{len}len:区间长度
懒标记处理
我们需要处理两种类型的懒标记:
- 覆盖标记:优先级最高,会清除所有线性标记
- 线性标记:可以累积,表示需要叠加的等差数列
在 pushDown\texttt{pushDown}pushDown 操作时:
- 先处理覆盖标记(如果有)
- 再处理线性标记(如果有)
时间复杂度
使用线段树后,每个操作的时间复杂度为 O(logN)O(\log N)O(logN),总时间复杂度为 O(TlogN)O(T \log N)O(TlogN),在 T=4×105T = 4 \times 10^5T=4×105,N=2.5×105N = 2.5 \times 10^5N=2.5×105 的情况下是可行的。
代码实现
// Rip Van Winkle's Code
// UVa ID: 12436
// Verdict: Accepted
// Submission Date: 2025-11-30
// UVa Run Time: 0.200s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 250005;
struct Node {
long long sum; // 区间和
long long cover; // 覆盖标记,INF表示未覆盖
long long k, b; // 线性标记:k*i + b
int len; // 区间长度
} tree[MAXN * 4];
const long long INF = 1e18;
void build(int node, int l, int r) {
tree[node] = {0, INF, 0, 0, r - l + 1};
if (l == r) return;
int mid = (l + r) >> 1;
build(node << 1, l, mid);
build(node << 1 | 1, mid + 1, r);
}
// 计算线性函数在区间[l, r]上的和
inline long long calcLinearSum(int l, int r, long long k, long long b) {
return k * (l + r) * (r - l + 1) / 2 + b * (r - l + 1);
}
// 应用覆盖标记
void applyCover(int node, int l, int r, long long val) {
tree[node].sum = (r - l + 1) * val;
tree[node].cover = val;
tree[node].k = tree[node].b = 0;
}
// 应用线性标记
void applyLinear(int node, int l, int r, long long k, long long b) {
tree[node].sum += calcLinearSum(l, r, k, b);
tree[node].k += k;
tree[node].b += b;
}
// 下推懒标记
void pushDown(int node, int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
int left = node << 1, right = node << 1 | 1;
// 先处理覆盖标记(优先级最高)
if (tree[node].cover != INF) {
applyCover(left, l, mid, tree[node].cover);
applyCover(right, mid + 1, r, tree[node].cover);
tree[node].cover = INF;
}
// 再处理线性标记
if (tree[node].k != 0 || tree[node].b != 0) {
applyLinear(left, l, mid, tree[node].k, tree[node].b);
applyLinear(right, mid + 1, r, tree[node].k, tree[node].b);
tree[node].k = tree[node].b = 0;
}
}
// 区间覆盖更新
void updateCover(int node, int l, int r, int ql, int qr, long long val) {
if (ql <= l && r <= qr) {
applyCover(node, l, r, val);
return;
}
pushDown(node, l, r);
int mid = (l + r) >> 1;
if (ql <= mid) updateCover(node << 1, l, mid, ql, qr, val);
if (qr > mid) updateCover(node << 1 | 1, mid + 1, r, ql, qr, val);
tree[node].sum = tree[node << 1].sum + tree[node << 1 | 1].sum;
}
// 区间线性更新
void updateLinear(int node, int l, int r, int ql, int qr, long long k, long long b) {
if (ql <= l && r <= qr) {
applyLinear(node, l, r, k, b);
return;
}
pushDown(node, l, r);
int mid = (l + r) >> 1;
if (ql <= mid) updateLinear(node << 1, l, mid, ql, qr, k, b);
if (qr > mid) updateLinear(node << 1 | 1, mid + 1, r, ql, qr, k, b);
tree[node].sum = tree[node << 1].sum + tree[node << 1 | 1].sum;
}
// 区间查询
long long query(int node, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) return tree[node].sum;
pushDown(node, l, r);
int mid = (l + r) >> 1;
long long res = 0;
if (ql <= mid) res += query(node << 1, l, mid, ql, qr);
if (qr > mid) res += query(node << 1 | 1, mid + 1, r, ql, qr);
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
build(1, 1, 250000);
while (t--) {
char op;
cin >> op;
if (op == 'A') {
int st, nd;
cin >> st >> nd;
updateLinear(1, 1, 250000, st, nd, 1, -st + 1);
} else if (op == 'B') {
int st, nd;
cin >> st >> nd;
updateLinear(1, 1, 250000, st, nd, -1, nd + 1);
} else if (op == 'C') {
int st, nd, x;
cin >> st >> nd >> x;
updateCover(1, 1, 250000, st, nd, x);
} else {
int st, nd;
cin >> st >> nd;
cout << query(1, 1, 250000, st, nd) << '\n';
}
}
return 0;
}
关键优化点
- 数学公式计算:使用等差数列求和公式在 O(1)O(1)O(1) 时间内计算区间和,避免了循环计算
- 懒标记设计:合理设计覆盖标记和线性标记的优先级和处理顺序
- 代码优化:使用内联函数、减少不必要的函数调用等技巧
总结
本题的关键在于将区间上的等差数列操作转化为线性函数的叠加,并利用线段树高效处理。通过数学建模和合理的数据结构设计,我们将原本 O(T×N)O(T \times N)O(T×N) 的时间复杂度优化到了 O(TlogN)O(T \log N)O(TlogN),成功解决了 Rip Van Winkle\texttt{Rip Van Winkle}Rip Van Winkle 的慢速代码问题。

223

被折叠的 条评论
为什么被折叠?



