珂朵莉树学习笔记
珂朵莉树又叫老司机树。
思路十分简单,众所周知,越暴力的数据结构能干的东西也就越多。
你看隔壁分块啥都能干
珂朵莉树最重要的有 个操作:
-
Spilt
操作,将区间分裂 -
Cover
操作,将区间覆盖
珂朵莉能用的前提是保证 Cover
操作的数量,所以你想卡 ODT
只需要把 Cover
的数量减少就行。
操作:
分裂操作 将区间 分为 和 最后返回的是 这个区间的迭代器。
首先我们要找到 pos
这个点的位置才能确定分裂哪个操作。
如果我们找到包含 pos
的区间的左端点正好是 pos
,那直接返回这个区间。
如果不是,那么 pos
就在上一个区间,先将迭代器向前进一步,然后将原先的区间删除,将新的两个区间加入。
It Spilt(int pos) { /*分裂操作 将区间 [l, r] 分为 [l, pos - 1] 和 [pos, r] 最后返回的是 [pos, r] 这个区间的迭代器*/ It it = s.lower_bound(node(pos)); // 二分找到第一个大于等于 pos 的区间 if(it != s.end() && it->l == pos) return it; // 如果区间的左端点正好是 pos 直接返迭代器 --it; // 如果不是pos 就在前一个区间 int L = it -> l, R = it -> r, V = it -> v; s.erase(it); // 将原先的区间删掉 s.insert(node(L, pos - 1, V)); return s.insert(node(pos, R, V)).first; /*s.insert 返回的是一个 pair 类型, 返回的就是 [pos, r] 这个区间的迭代器*/ }
操作:
覆盖操作。
代码虽短,但这是时间复杂度的保证。
先把 这一段区间分出来,里面可能会包括很多区间,将这些区间都删除,然后加入一个新的维护的区间。
每一次操作都能将区间个数减少 。
void Cover(int l, int r, int Val) { It it2 = Spilt(r + 1), it1 = Spilt(l); s.erase(it1, it2); s.insert(node(l, r, Val)); // 把分开的区间中间的全部删掉,然后插入新的 // 这是ODT复杂度的保证 每次能有效的减少 set 内的个数 }
来个例题代码:
CF896C
/* Work by: TLE_Automation */ #include<set> #include<cmath> #include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define LL long long #define int long long using namespace std; //const int N = 1e6 + 10; //const int MAXN = 2e5 + 10; inline char readchar() { static char buf[100000], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++; } inline int read() { #define readchar getchar int res = 0, f = 0;char ch = readchar(); for(; !isdigit(ch); ch = readchar()) if(ch == '-') f = 1; for(; isdigit(ch); ch = readchar()) res = (res << 1) + (res << 3) + (ch ^ '0'); return f ? -res : res; } inline void print(int x) { if (x < 0 ) putchar('-'), x = -x; if (x > 9 ) print(x / 10); putchar(x % 10 + '0'); } int Qpow(int a, int b, int mod) { int base = a % mod, res = 1; while(b) { if(b & 1) res = (res * base) % mod; base = (base * base) % mod, b >>= 1; }return res % mod; } int n, m, seed, vmax; const int Mod = 1e9 + 7; int Rand() { int ret = seed; seed = (seed * 7 + 13) % Mod; return ret; } namespace Chtholly_Tree { struct node { int l, r; mutable int v; // mutable 是可变的意思,不加后面调用迭代器会CE node (int L, int R = -1, int V = 0) : l(L), r(R), v(V) {} // 初始化 bool operator < (const node &x) const { return l < x.l; } // set 里用结构体要重载小于号 }; #define It set<node>::iterator set <node> s; It Spilt(int pos) { /* 分裂操作 将区间分为 [l, pos - 1] 和 [pos, r] 最后返回的是 [pos, r] 这个区间的迭代器 */ It it = s.lower_bound(node(pos)); // 二分找到第一个大于等于 pos 的区间 if(it != s.end() && it->l == pos) return it; // 如果区间的左端点正好是 pos 直接返迭代器 --it; // 如果不是pos 就在前一个区间 int L = it -> l, R = it -> r, V = it -> v; s.erase(it); // 将原先的区间删掉 s.insert(node(L, pos - 1, V)); return s.insert(node(pos, R, V)).first; /* s.insert 返回的是一个 pair 类型 返回的就是 [pos, r] 这个区间的迭代器 */ } void Add(int l, int r, int Val) { It it2 = Spilt(r + 1), it1 = Spilt(l); for(; it1 != it2; ++it1) it1 -> v += Val; // 暴力加 } void Cover(int l, int r, int Val) { It it2 = Spilt(r + 1), it1 = Spilt(l); s.erase(it1, it2); s.insert(node(l, r, Val)); // 把分开的区间中间的全部删掉,然后插入新的 // 这是ODT复杂度的保证 每次能有效的建设 set 内的个数 } int Rank(int l, int r, int k) { /* 将这部分区间全放到一个 vector 容器里 然后暴力找第 k 个 */ vector <pair<int, int > > vec; It it2 = Spilt(r + 1), it1 = Spilt(l); vec.clear(); for(; it1 != it2; ++it1) vec.push_back(pair <int, int> (it1 -> v, it1 -> r - it1 -> l + 1)); sort(vec.begin(), vec.end()); for(vector <pair <int, int> >::iterator it = vec.begin(); it != vec.end(); ++it) { k -= it -> second; if(k <= 0) return it -> first; } } int Query(int l, int r, int p, int mod) { It it2 = Spilt(r + 1), it1 = Spilt(l); int res = 0; // 暴力求幂次和 for(; it1 != it2; ++it1) res = (res + (it1 -> r - it1 -> l + 1) * Qpow(it1 -> v, p, mod) ) % mod; return res; } } using namespace Chtholly_Tree; signed main() { n = read(), m = read(), seed = read(), vmax = read(); for(int i = 1; i <= n; i++) { int x = (Rand() % vmax)+ 1; s.insert(node(i, i, x)); } s.insert(node(n + 1, n + 1, 0)); for(int i = 1, opt, l, r, x, y; i <= m; ++i) { opt = (Rand() % 4) + 1, l = (Rand() % n) + 1, r = (Rand() % n) + 1; if(l > r) swap(l, r); if(opt == 3) x = (Rand() % (r - l + 1)) + 1; else x = (Rand() % vmax) + 1; if(opt == 4) y = (Rand() % vmax) + 1; if(opt == 1) { Add(l, r, x); } else if(opt == 2) { Cover(l, r, x); } else if(opt == 3) { print(Rank(l, r, x)), putchar('\n'); } else { print(Query(l, r, x, y)), putchar('\n'); } } return 0; }
本文作者:TLE_Automation
本文链接:https://www.cnblogs.com/tttttttle/p/16293523.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!