auto split(int pos) {
auto it = odt.lower_bound({pos, 0, 0});
if (it != odt.end() && it->l == pos) return it;
--it;
int L = it->l, R = it->r;
long long V = it->val;
odt.erase(it);
odt.insert({L, pos - 1, V});
return odt.insert({pos, R, V}).first;
}
示意图:
Before split(4): [1,3]:5 [4,7]:3 [8,10]:2
After split(4): [1,3]:5 [4,7]:3 [8,10]:2 (左端点在 4 无需分裂)
Before split(5): [1,3]:5 [4,7]:3 [8,10]:2
After split(5): [1,3]:5 [4,4]:3 [5,7]:3 [8,10]:2
void add(int l, int r, long long x) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it)
it->val += x;
}
long long sum(int l, int r) {
auto itr = split(r + 1), itl = split(l);
long long res = 0;
for (auto it = itl; it != itr; ++it)
res += it->val * (it->r - it->l + 1);
return res;
}
long long kth(int l, int r, int k) {
auto itr = split(r + 1), itl = split(l);
vector<pair<long long, int>> v;
for (auto it = itl; it != itr; ++it)
v.push_back({it->val, it->r - it->l + 1});
sort(v.begin(), v.end());
for (auto &p : v) {
k -= p.second;
if (k <= 0) return p.first;
}
return -1;
}
四、复杂度分析
情况
复杂度
随机数据 + 大量 assign
期望 O(mlogn)
仅有区间加/查询
退化至 O(nm)
构造卡 ODT 数据
O(nm) — 可被 Hack
为什么随机数据下高效?
assign 操作每次至少将 ≥2 个区间合为 1 个,区间总数快速收敛至 O(1)。
在随机数据下,期望区间数约为 O(logn) 或 O(n)。
每次 split 最多增加 1 个区间。
使用条件(三个缺一不可)
有区间赋值操作
数据随机生成(或题目明确不卡 ODT)
不会故意构造让区间数不减少的数据
五、完整模板
#include <bits/stdc++.h>
using namespace std;
struct Node {
int l, r;
mutable long long val;
bool operator<(const Node &o) const { return l < o.l; }
};
struct ODT {
set<Node> s;
auto split(int pos) {
auto it = s.lower_bound({pos, 0, 0});
if (it != s.end() && it->l == pos) return it;
if (it == s.begin()) return s.end();
--it;
int L = it->l, R = it->r;
long long V = it->val;
s.erase(it);
s.insert({L, pos - 1, V});
return s.insert({pos, R, V}).first;
}
void assign(int l, int r, long long val) {
auto itr = split(r + 1), itl = split(l);
s.erase(itl, itr);
s.insert({l, r, val});
}
void add(int l, int r, long long x) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it)
it->val += x;
}
long long query_sum(int l, int r) {
auto itr = split(r + 1), itl = split(l);
long long res = 0;
for (auto it = itl; it != itr; ++it)
res += it->val * (it->r - it->l + 1);
return res;
}
};
六、经典例题
例题 1:CF 896C — Willem, Chtholly and Seniorious
【题意】
维护一个长度为 n 的数组,支持四种操作(所有数据随机生成):
1 l r x:将 [l,r] 内所有元素加上 x
2 l r x:将 [l,r] 内所有元素赋值为 x
3 l r x:求 [l,r] 内第 x 小的数
4 l r x y:求 ∑i=lraixmody
n,m≤105,数据随机。
【思路】
标准 ODT 板题。四种操作均可直接套用区间遍历模板。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const i64 MOD = 1000000007;
i64 qpow(i64 a, i64 b, i64 mod) {
a %= mod;
i64 res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
struct Node {
int l, r;
mutable i64 val;
bool operator<(const Node &o) const { return l < o.l; }
};
set<Node> odt;
auto split(int pos) {
auto it = odt.lower_bound({pos, 0, 0});
if (it != odt.end() && it->l == pos) return it;
--it;
int L = it->l, R = it->r;
i64 V = it->val;
odt.erase(it);
odt.insert({L, pos - 1, V});
return odt.insert({pos, R, V}).first;
}
void assign(int l, int r, i64 val) {
auto itr = split(r + 1), itl = split(l);
odt.erase(itl, itr);
odt.insert({l, r, val});
}
void add(int l, int r, i64 x) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it)
it->val += x;
}
i64 kth(int l, int r, int k) {
auto itr = split(r + 1), itl = split(l);
vector<pair<i64, int>> v;
for (auto it = itl; it != itr; ++it)
v.push_back({it->val, it->r - it->l + 1});
sort(v.begin(), v.end());
for (auto &p : v) {
k -= p.second;
if (k <= 0) return p.first;
}
return -1;
}
i64 sum_pow(int l, int r, i64 x, i64 y) {
auto itr = split(r + 1), itl = split(l);
i64 res = 0;
for (auto it = itl; it != itr; ++it)
res = (res + qpow(it->val, x, y) * (it->r - it->l + 1)) % y;
return res;
}
int n, m;
i64 seed, vmax;
i64 rnd() {
i64 ret = seed;
seed = (seed * 7 + 13) % MOD;
return ret;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> seed >> vmax;
for (int i = 1; i <= n; i++)
odt.insert({i, i, rnd() % vmax + 1});
while (m--) {
int op = rnd() % 4 + 1;
int l = rnd() % n + 1;
int r = rnd() % n + 1;
if (l > r) swap(l, r);
i64 x, y;
if (op == 3) x = rnd() % (r - l + 1) + 1;
else x = rnd() % vmax + 1;
if (op == 4) y = rnd() % vmax + 1;
if (op == 1) add(l, r, x);
else if (op == 2) assign(l, r, x);
else if (op == 3) cout << kth(l, r, x) << "\n";
else cout << sum_pow(l, r, x, y) << "\n";
}
return 0;
}
例题 2:CF 915E — Physical Education Lessons
【题意】
有 n 个工作日,初始全部为工作日。q 次操作,每次操作将区间 [l,r] 全部设为工作日 k=1 或非工作日 k=2。每次操作后输出当前工作日总数。
int n, q, ans;
void assign(int l, int r, int val) {
auto itr = split(r + 1), itl = split(l);
for (auto it = itl; it != itr; ++it)
ans -= (it->val == 1) * (it->r - it->l + 1); // 减去旧值
odt.erase(itl, itr);
odt.insert({l, r, val});
if (val == 1) ans += r - l + 1; // 加上新值
}