珂朵莉树
珂朵莉树思想大致为存储连续段信息,用 set 维护,再利用 set 特性暴力处理操作。比较擅长需要进行区间赋值、推平操作类型操作的题目,进行 \(k\) 次赋值、合并操作后的期望复杂度为 \(O(\frac{n}{k})\),详细证明,但可以粗略地记下他的复杂度:对于普通的区间加、区间赋值、简单的区间查询类操作,可以做到 \(O(n\log\log n)\) 的优秀期望复杂度。因此在随机数据下表现很好。作者偏爱一些比较暴力的数据结构,而在一些更加细节、有意思的部分认真思考,因此很久前学过。如今来重温练习一番。
板子
题意:给定一个长度为 \(n\) 的序列,要进行 \(m\) 次操作:区间加、区间赋值、区间 kth 和取模意义下 \(x\) 次方和。\(1\leq n,q\leq 10^5\)。
Code
// stODDDDDDDDDDDDDDDDDDDDDDDDDDD hzt CCCCCCCCCCCCCCCCCCCCCCCCCOrz
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
using PLI = pair<LL, int>;
constexpr int kN = 1e5 + 1, kP = 1e9 + 7;
int n, m, seed, vmx;
int a[kN];
struct Itv {
int l, r;
mutable LL w;
Itv(int _l = 0, int _r = 0, LL _w = 0) { l = _l, r = _r, w = _w; }
bool operator<(const Itv &x) const { return l < x.l; }
};
set<Itv> t;
auto S(int p) {
auto i = t.lower_bound(Itv(p));
if (i != t.end() && i->l == p) {
return i;
}
i--;
if (i->r < p) {
return t.end();
}
int l = i->l, r = i->r;
LL w = i->w;
t.erase(i);
t.emplace(l, p - 1, w);
return t.emplace(p, r, w).first;
}
void U(int l, int r, int w) {
auto ir = S(r + 1), il = S(l);
for (auto i = il; i != ir; i++) {
i->w += w;
}
}
void A(int l, int r, int w) {
auto ir = S(r + 1), il = S(l);
t.erase(il, ir);
t.emplace(l, r, w);
}
LL K(int l, int r, int w) {
auto ir = S(r + 1), il = S(l);
vector<PLI> v;
for (auto i = il; i != ir; i++) {
v.emplace_back(i->w, i->r - i->l + 1);
}
sort(v.begin(), v.end());
for (auto [c, l] : v) {
if (l < w) {
w -= l;
} else {
return c;
}
}
return -1;
}
int P(LL a, int b, int p) {
int ret = 1;
a %= p;
for (; b > 0; b /= 2) {
if (b & 1) {
ret = ret * a % p;
}
a = a * a % p;
}
return ret;
}
LL Q(int l, int r, int x, int y) {
auto ir = S(r + 1), il = S(l);
LL ret = 0;
for (auto i = il; i != ir; i++) {
ret = (ret + 1ll * P(i->w, x, y) * (i->r - i->l + 1) % y) % y;
}
return ret;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m >> seed >> vmx;
auto rnd = [&]() {
int ret = seed;
seed = (7ll * seed + 13) % kP;
return ret;
};
for (int i = 1; i <= n; i++) {
a[i] = rnd() % vmx + 1;
t.emplace(i, i, a[i]);
}
for (int i = 1, o, l, r, x, y; i <= m; i++) {
o = rnd() % 4 + 1;
l = rnd() % n + 1, r = rnd() % n + 1;
if (l > r) {
swap(l, r);
}
if (o == 3) {
x = rnd() % (r - l + 1) + 1;
} else {
x = rnd() % vmx + 1;
}
if (o == 4) {
y = rnd() % vmx + 1;
}
if (o == 1) {
U(l, r, x);
} else if (o == 2) {
A(l, r, x);
} else if (o == 3) {
cout << K(l, r, x) << '\n';
} else {
cout << Q(l, r, x, y) << '\n';
}
}
return 0;
}
T1
题意:给定一个长度为 \(n\) 小写字母串,进行 \(q\) 次操作:区间升/降序排序。问最终字符串。\(1\leq n\leq 10^5, 1\leq q\leq 5\times 10^4\)。
Solution
修改时只需要把这段区间内的所有颜色统计个数,然后按照字母序一一放回去即可。
由于 ODT 中总共最多产生 \(n + 26q\) 个区间,总时间复杂度大致为 \(O((n + 26q)\log\log n)\),足以通过此题。
\(\\\)
Code
// stODDDDDDDDDDDDDDDDDDDDDDDDDDD hzt CCCCCCCCCCCCCCCCCCCCCCCCCOrz
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 1e5 + 1, kA = 26;
int n, q;
string s;
struct Itv {
int l, r;
char c;
Itv(int _l = 0, int _r = 0, char _c = '#') { l = _l, r = _r, c = _c; }
bool operator<(const Itv &x) const { return l < x.l; }
};
set<Itv> t;
auto S(int p) {
auto i = t.lower_bound(Itv(p));
if (i != t.end() && i->l == p) {
return i;
}
i--;
if (i->r < p) {
return t.end();
}
int l = i->l, r = i->r;
char c = i->c;
t.erase(i);
t.emplace(l, p - 1, c);
return t.emplace(p, r, c).first;
}
int b[kA];
void U(int l, int r, bool o) {
auto ir = S(r + 1), il = S(l);
for (auto i = il; i != ir; i++) {
b[i->c - 'a'] += i->r - i->l + 1;
}
t.erase(il, ir);
if (o) {
for (int i = 0, j = l; i < kA; i++) {
if (b[i]) {
t.emplace(j, j + b[i] - 1, i + 'a');
j += b[i], b[i] = 0;
}
}
} else {
for (int i = kA - 1, j = l; i >= 0; i--) {
if (b[i]) {
t.emplace(j, j + b[i] - 1, i + 'a');
j += b[i], b[i] = 0;
}
}
}
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> q >> s;
s = '#' + s;
for (int i = 1; i <= n; i++) {
t.emplace(i, i, s[i]);
}
for (int i = 1, l, r, o; i <= q; i++) {
cin >> l >> r >> o;
U(l, r, o);
}
for (auto v : t) {
for (int i = v.l; i <= v.r; i++) {
cout << v.c;
}
}
return 0;
}
T2
题意:给定一颗 \(n\) 个节点、以 \(1\) 为根的树,每个点有 \(0/1\) 作为权值,初始为 \(0\),进行 \(q\) 次操作:将点 \(x\) 子树内点赋值为 \(1\)、点 \(x\) 到根节点 \(1\) 的路径上点赋值为 \(0\)、单点查询权值。\(1\leq n,q\leq 5\times 10^5\)。
Solution
考虑树剖。
于是操作一变成了区间赋值(子树内 dfs 序连续),操作二直接暴力跳重链,操作三直接找到点所在的区间返回权值就行了。
时间复杂度 \(O(n + q\log n\log\log n)\)?
\(\\\)
Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 5e5 + 1;
int n, q;
vector<int> e[kN];
int dep[kN], sz[kN], f[kN];
int dfn[kN], dfc, tp[kN];
void D1(int x, int fa) {
dep[x] = dep[fa] + 1;
f[x] = fa, sz[x] = 1;
for (auto v : e[x]) {
if (v == fa) {
continue;
}
D1(v, x);
sz[x] += sz[v];
}
}
void D2(int x, int fa, int top) {
tp[x] = top, dfn[x] = ++dfc;
int mx = -1;
for (auto v : e[x]) {
if (v != fa && sz[v] > sz[mx]) {
mx = v;
}
}
if (mx != -1) {
D2(mx, x, top);
}
for (auto v : e[x]) {
if (v != fa && v != mx) {
D2(v, x, v);
}
}
}
struct Itv {
int l, r;
mutable bool w;
Itv(int _l = 0, int _r = 0, bool _w = 0) { l = _l, r = _r, w = _w; }
bool operator<(const Itv &x) const { return l < x.l; }
};
set<Itv> t;
auto S(int p) {
auto i = t.lower_bound(p);
if (i != t.end() && i->l == p) {
return i;
}
i--;
int l = i->l, r = i->r;
bool w = i->w;
t.erase(i);
t.emplace(l, p - 1, w);
return t.emplace(p, r, w).first;
}
void A(int l, int r, int w) {
auto ir = S(r + 1), il = S(l);
t.erase(il, ir);
t.emplace(l, r, w);
}
void U(int u, int v, int w) {
for (; tp[u] != tp[v]; u = f[tp[u]]) {
if (dep[tp[u]] < dep[tp[v]]) {
swap(u, v);
}
A(dfn[tp[u]], dfn[u], w);
}
if (dep[u] > dep[v]) {
swap(u, v);
}
A(dfn[u], dfn[v], w);
}
int Q(int x) {
auto i = t.lower_bound(x);
if (i == t.end()) {
return 0;
} else if (i != t.end() && i->l == x) {
return i->w;
} else {
return prev(i)->w;
}
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n;
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
D1(1, 0), D2(1, 0, 1);
t.emplace(1, n, 0);
cin >> q;
for (int _ = 1, o, x; _ <= q; _++) {
cin >> o >> x;
if (o == 1) {
A(dfn[x], dfn[x] + sz[x] - 1, 1);
} else if (o == 2) {
U(x, 1, 0);
} else {
cout << Q(dfn[x]) << '\n';
}
}
return 0;
}
T3
题意:给定一个长度为 \(n\) 的序列 \(b\) 和一个整数 \(k\),将 \(b\) 序列复制 \(k\) 次成为长度为 \(nk\) 的初始序列 \(a\)。有 \(q\) 次操作:区间赋值、区间取最小值。\(1\leq n, q\leq 10^5, 1\leq k\leq 10^4\)。
Solution
序列长度很长,但维护信息不多,重复量很大。考虑用 ODT 维护修改,而未修改部分使用 ST 表计算即可。时间复杂度 \(O(n\log n + q\log\logn)\)。
\(\\\)
Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 1e5 + 2, kB = 17, kI = 1e9;
int n, k, q;
int f[kB][kN], lg[kN], s[kN], p[kN], bmn = kI, mn = kI;
int Min(int l, int r) {
if (r - l + 1 >= n) {
return bmn;
}
l = (l - 1) % n + 1, r = (r - 1) % n + 1;
if (l > r) {
return min(p[r], s[l]);
}
int g = lg[r - l + 1];
return min(f[g][l], f[g][r - (1 << g) + 1]);
}
struct Itv {
int l, r, w;
bool t;
Itv(int _l = 0, int _r = 0, int _w = 0, bool _t = 0) { l = _l, r = _r, w = _w, t = _t; }
bool operator<(const Itv &x) const {
return l < x.l;
}
};
set<Itv> t;
auto S(int p) {
auto i = t.lower_bound(p);
if (i != t.end() && i->l == p) {
return i;
}
i--;
if (i->r < p) {
return t.end();
}
int l = i->l, r = i->r, w = i->w;
bool f = i->t;
t.erase(i);
t.emplace(l, p - 1, w, f);
return t.emplace(p, r, w, f).first;
}
void A(int l, int r, int w) {
auto ir = S(r + 1), il = S(l);
t.erase(il, ir);
t.emplace(l, r, w, 1);
}
int Q(int l, int r) {
auto ir = S(r + 1), il = S(l);
int ret = kI;
for (auto i = il; i != ir; i++) {
if (i->t) {
ret = min(ret, i->w);
} else {
ret = min(ret, Min(i->l, i->r));
}
if (ret == mn) {
break;
}
}
return ret;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> f[0][i];
bmn = min(bmn, f[0][i]);
}
mn = bmn, lg[0] = -1;
for (int i = 1; i <= n; i++) {
lg[i] = lg[i / 2] + 1;
}
p[0] = s[n + 1] = kI;
for (int i = 1; i <= n; i++) {
p[i] = min(p[i - 1], f[0][i]);
}
for (int i = n; i >= 1; i--) {
s[i] = min(s[i + 1], f[0][i]);
}
for (int i = 1; i < kB; i++) {
for (int j = 1; j + (1 << i) <= n + 1; j++) {
f[i][j] = min(f[i - 1][j], f[i - 1][j + (1 << i - 1)]);
}
}
t.emplace(1, k * n, bmn, 0);
cin >> q;
for (int _ = 1, o, l, r, w; _ <= q; _++) {
cin >> o >> l >> r;
if (o == 1) {
cin >> w;
mn = min(mn, w);
A(l, r, w);
} else {
cout << Q(l, r) << '\n';
}
}
return 0;
}
T4
题意:有一个 \(n\) 个元素的序列,每个元素有其相应的颜色 \(c_i\) 和值 \(w_i\),初始分别为 \(i\) 和 \(0\)。将进行 \(m\) 次操作:区间修改(对于所有 \(i\in[l,r], w_i\) 加上 \(|c_i-x|\),\(c_i\) 变为 \(x\))、查询区间权值和。\(1\leq n,m\leq 10^5\)。
Solution
还是可以 ODT。考虑用 ODT 维护颜色,线段树维护权值:修改在 ODT 上区间赋值时把区间的颜色和 \(x\) 的差放线段树上区间加,查询直接线段树上查询区间和就行了。时间复杂度 \(O(q\log^2n)\)。
\(\\\)
Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>
#include <vector>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 1e5 + 1;
int n, m;
struct Segment {
struct Node {
LL w, t;
} t[4 * kN];
void PU(int x) { t[x].w = t[2 * x].w + t[2 * x + 1].w; }
void PD(int x, int l, int r) {
t[2 * x].t += t[x].t, t[2 * x + 1].t += t[x].t;
int m = (l + r) / 2;
t[2 * x].w += t[x].t * (m - l + 1);
t[2 * x + 1].w += t[x].t * (r - m);
t[x].t = 0;
}
void U(int L, int R, int w, int x = 1, int l = 1, int r = n) {
if (r < L || R < l) {
return;
} else if (L <= l && r <= R) {
t[x].w += w * (r - l + 1ll), t[x].t += w;
return;
}
int m = (l + r) / 2;
PD(x, l, r);
U(L, R, w, 2 * x, l, m);
U(L, R, w, 2 * x + 1, m + 1, r);
PU(x);
}
LL Q(int L, int R, int x = 1, int l = 1, int r = n) {
if (r < L || R < l) {
return 0;
} else if (L <= l && r <= R) {
return t[x].w;
}
int m = (l + r) / 2;
PD(x, l, r);
return Q(L, R, 2 * x, l, m) + Q(L, R, 2 * x + 1, m + 1, r);
}
} st;
struct Itv {
int l, r, c;
Itv(int _l = 0, int _r = 0, int _c = 0) { l = _l, r = _r, c = _c; }
bool operator<(const Itv &x) const { return l < x.l; }
};
set<Itv> t;
auto S(int p) {
auto i = t.lower_bound(p);
if (i != t.end() && i->l == p) {
return i;
}
i--;
if (i->r < p) {
return t.end();
}
int l = i->l, r = i->r, c = i->c;
t.erase(i);
t.emplace(l, p - 1, c);
return t.emplace(p, r, c).first;
}
void A(int l, int r, int w) {
auto ir = S(r + 1), il = S(l);
for (auto i = il; i != ir; i++) {
st.U(i->l, i->r, abs(i->c - w));
}
t.erase(il, ir);
t.emplace(l, r, w);
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
t.emplace(i, i, i);
}
for (int i = 1, o, l, r, x; i <= m; i++) {
cin >> o >> l >> r;
if (o == 1) {
cin >> x;
A(l, r, x);
} else {
cout << st.Q(l, r) << '\n';
}
}
return 0;
}
最后
可能是因为 ODT 太暴力了,现在出题人都不太看好这种做法。作者写了 CF1136E 和 LG P5500 后都被卡飞,其实都挺有意思的。或许 ODT 只在诞生之时存活吧。还是线段树更实用一些。