前言
多学一点数据结构一般是有好处的,至少可以帮你打暴力
——Wei_taming
因此,作者开始补习DL数据结构
什么是珂朵莉?
珂朵莉是世界上最幸福的女孩子,没有之一,不接受任何反驳!
什么是珂朵莉树?
珂朵莉树原名ODT(Ord Driver Tree)
,即老司机树。是一种基于set
的暴力数据结构,起源于Codeforces-896C
其原理在于使用STL
中的set
来维护一段连续的相同元素。
它的时间复杂度其实是假的,但在数据随机时可以有很好的表现。
而且珂朵莉树使用的局限性较大,如果没有区间赋值操作,则珂朵莉树就毫无用武之地。
因此,珂朵莉树大部分情况用于骗分。
珂朵莉树的核心操作
定义节点
#define ll long long struct Node{ int l , r; mutable ll val; bool operator < (const Node & b) const{ return l < b.l } }; set<Node > S;
在上面的代码中,每一个Node
都是在维护一段连续的区间,这些区间的大小不等。
其中,代表区间左端点,代表区间右端点,代表当前这段区间每一个元素的值.
顺带提一句,mutable
可以直接对迭代器进行修改而不影响里边的值。
最后重载运算符,调整每一个节点在set
中的存储顺序。
这样,我们就完成了珂朵莉树的节点创建。
建树
对于建树,我们只需要把区间中的每一个数看做一段长度为的区间,然后暴力插入即可
Code
ll a[10001]; void build(int n){ for(int i = 1; i <= n; i ++){ s.insert((Node){i , i , a[i]}); } }
分裂(Spilt)
操作可以将含有位置的区间分为两部分,分别为与
Code
#define range set<Node > S :: iterator range spilt(int pos){ range it = S.lower_bound((Node){pos , pos , -1}); if(it != S.end() && it -> l == pos){ return it; } it --; Node ins = *it; S.erase(it); S.insert((Node){ins.l , pos - 1 , ins.val}); return S.insert((Node){pos , ins.r , ins.val}).first; }
上面的函数的返回值为一个迭代器,代表从这一段区间,方便之后的操作。
在函数的开头,我们需要寻找第一个左端点大于等于的节点。
之后对其进行特判:如果说找到的这段区间不在珂朵莉树的末尾,而且找到的这个节点的,则证明我们找到了这个节点,直接返回即可。
否则,则刚刚找到的这个节点的一定大于(我们使用的是lower_bound
,找到的那段区间的一定大于等于,而等于的情况已经被我们特判掉)。因此我们需要返回到上一段区间进行寻找。
之后需要一个替代变量存储我们刚刚找到的区间,然后将原来的的区间删掉,再插入与,最后返回以为左端点的区间,即。
现在,我们就完成了珂朵莉树的分裂操作。
区间推平(Assign)
现在,我们已经可以轻松分裂一颗珂朵莉树。
但随着珂朵莉树的不断分裂,set
中的元素也会渐渐增多,从而不断增大珂朵莉树的时间复杂度。
因此,我们还需要来降低时间复杂度。
操作的用处在于推平一段区间,说白了,就是将一个区间全部修改为一个值,从而合并珂朵莉树。
在数据随机的情况下,操作的出现频率并不小,大约在上下。而且操作可以一次合并多个节点,可以将set
的大小控制在一个可控的范围之内。因此,操作是珂朵莉树的重要保证。
Code
void assign(int l , int r , ll val){ range R = spilt(r + 1); range L = spilt(l); S.erase(L , R); S.insert(Node{l , r , val}); }
首先,我们分别分裂左右区间。(要注意,这里需要先分裂右区间。因为在分离左区间时会导致迭代器失效,若这时强行分裂,会导致程序)
之后我们分别删除我们分离出来的左右区间,最后将整段区间加入即可。
珂朵莉树的其他操作
下面我们就以珂朵莉树的起源:Codeforces-896C为例,讲解珂朵莉树的其他操作。
区间加
直接分裂出区间端点,然后暴力解决即可
void range_add(int l , int r , int val){ range R = spilt(r + 1); range L = spilt(l); for(range i = L; i != R; i ++){ i -> val += val; } }
区间快速幂之和
即求:$$
对每一段区间暴力快速幂即可。
ll Pow(ll a , ll n , ll HA){ if(n == 0){ return 1; } if(n == 1){ return a % HA; } ll c = Pow(a , n / 2 , HA); if(n % 2 == 0){ return c * c % HA; } else{ return (c * c * a) % HA; } } ll range_pow(int l , int r , ll val , ll HA){ range R = spilt(r + 1); range L = spilt(l); ll ans = 0; for(range i = L; i != R; i ++){ ans += (ll)(i -> r - i -> l + 1) * Pow(i -> v , val , HA); ans %= HA; } return ans; }
区间第k大
原理也很简单,就是把那段区间的元素丢入一个vector
,然后排序。
ll range_query(int l , int r , int k){ #define pairs pair<ll , int> vector<pairs > V; V.clear(); range R = spilt(r + 1); range L = spilt(l); for(range i = L; i != R; i ++){ V.push_back(pairs(i -> val , i -> r - i -> l + 1)); } sort(V.begin() , V.end()); ll sum = 0; for(vector<pairs > :: iterator i = V.begin(); i != V.end(); i ++){ sum += i -> second; if(sum >= k){ return i -> first; } } return -1; }
操作模板
不知道大家看出来了没有,珂朵莉树的操作大部分是有模板可以套的。
模板如下
void change(int l , int r , int val){ range R = spilt(r + 1); range L = spilt(l); for(range i = L; i != R; i ++){ ……………… } }
Code[Ctholly Tree]
最后放上珂朵莉树起源的代码好了(为啥我觉得一半代码都在写数据生成器???)
题目链接:Codeforces-896C
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<stack> #include<queue> #include<deque> #include<vector> #include<algorithm> #include<iomanip> #include<cstdlib> #include<cctype> #include<cmath> #include<set> #define ll long long #define I inline #define N 100001 using namespace std; int n , m , s; int a[N]; namespace ODT{ #define range set<Node> :: iterator struct Node{ int l , r; mutable ll val; bool operator < (const Node &b) const{ return l < b.l; } }; set<Node > S; void build(int n){ for(int i = 1; i <= n; i ++){ S.insert(Node{i , i , a[i]}); } } range spilt(int pos){ range it = S.lower_bound((Node){pos , pos , -1}); if(it != S.end() && it -> l == pos){ return it; } it --; Node ins = *it; S.erase(it); S.insert((Node){ins.l , pos - 1 , ins.val}); return S.insert((Node){pos , ins.r , ins.val}).first; } void assign(int l , int r , ll val){ range R = spilt(r + 1); range L = spilt(l); S.erase(L , R); S.insert(Node{l , r , val}); } void range_add(int l , int r , int val){ range R = spilt(r + 1); range L = spilt(l); for(range i = L; i != R; i ++){ i -> val += val; } } ll Pow(ll a , ll n , ll HA){ if(n == 0){ return 1; } if(n == 1){ return a % HA; } ll c = Pow(a , n / 2 , HA) % HA; if(n % 2 == 0){ return (c * c) % HA; } else{ return (((c * c) % HA) * a) % HA; } } ll range_pow(int l , int r , ll val , ll HA){ range R = spilt(r + 1); range L = spilt(l); ll ans = 0; for(range i = L; i != R; i ++){ ans += (ll)(i -> r - i -> l + 1) * Pow(i -> val , val , HA); ans %= HA; } return ans; } ll range_query(int l , int r , int k){ #define pairs pair<ll , int> vector<pairs > V; V.clear(); range R = spilt(r + 1); range L = spilt(l); for(range i = L; i != R; i ++){ V.push_back(pairs(i -> val , i -> r - i -> l + 1)); } sort(V.begin() , V.end()); ll sum = 0; for(vector<pairs > :: iterator i = V.begin(); i != V.end(); i ++){ sum += i -> second; if(sum >= k){ return i -> first; } } return -1; } } ll seed, vmax; I ll rnd(){ ll ret = seed; seed = (seed * 7 + 13) % 1000000007; return ret; } int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m >> seed >> vmax; for (ll i = 1; i <= n; i ++){ ODT::S.insert(ODT :: Node{i, i, (rnd() % vmax) + 1}); } ll x, y; while (m --){ x = 0, y = 0; ll op = (rnd() % 4) + 1, l = (rnd() % n) + 1, r = (rnd() % n) + 1; if (l > r){ swap(l , r); } if (op == 3){ x = (rnd() %(r - l + 1)) + 1; } else{ x = (rnd() % vmax) + 1; } if (op == 4){ y = (rnd() % vmax) + 1; } switch (op){ case 1: ODT :: range_add(l, r, x); break; case 2: ODT :: assign(l, r, x); break; case 3: cout << ODT :: range_query(l, r, x) << endl; break; case 4: cout << ODT :: range_pow(l, r, x, y) << endl; break; } } return 0; }