[数据结构/算法]珂朵莉树Chtholly_Tree/ODT
珂朵莉树,源自CF896C lxl写的一篇题解中将这种“没人见过的数据结构“称为Chtholly Tree/ODT
基于Std::set 本质上是巧妙的暴力
将元素相同的相邻元素作为同一个结点储存在set中
借由起源题说明。
CF896C要求实现以下4种操作
1.区间赋值 2.区间加数 3.求区间第k大 4.求区间幂次和
n个数 m次操作
n, m <= 1e5 ,2sec 数据随机
珂朵莉树对于此题中2、3、4操作均为暴力做法
即取出set中相应区间的结点,逐个进行暴力操作
区间赋值-推平区间[l, r](即将这些结点全部erase, 加入一个新的结点)
区间加数-取出区间[l, r]的结点 值加上相应的数
区间第k大 取出[l, r]结点 排序找第k大
区间幂次和 取出[l, r]结点 逐一ksm求幂加和
有时候区间并非一定是当前已存储结点的边界,故还需要一种操作将树中结点分裂
因为数据随机,数据中会有许多次将set中结点推平使得珂朵莉树在进行其他操作时复杂度相较于一般暴力实际上跑的很快
在数据随机的情况下复杂度约O(mlogn)
(so'……也非常容易被卡,只需要人为的一次推平也不给&初始数据也造的过分点 就能玩死珂朵莉树了……)
珂朵莉树本体(结点)
struct node{ int l, r; mutable char v; node(int L, int R = -1, char V = 0):l(L), r(R), v(V){} bool operator < (const node& o) const{ return l < o.l; } }; set<node> s;
核心代码1 分裂结点split(取出pos所在结点的迭代器,若pos不为某个结点的左端点就找到pos所在结点将其分裂成两个结点并返回该结点迭代器)
set<node>::iterator split(int pos){ auto it = s.lower_bound(node(pos)); if(it != s.end() && it->l == pos) return it; --it; if(pos > it->r) return s.end(); int L = it->l, R = it->r; char v = it->v; s.erase(it); s.insert(node(L, pos-1, v)); return s.insert(node(pos, R, v)).first; }
核心代码2 区间赋值(推平) 珂朵莉树的核心操作,时间复杂度的保证
void assign(int l, int r, char val){ //split(l); auto itr = split(r+1), itl = split(l); s.erase(itl, itr); s.insert(node(l, r, val)); }
其他的操作只需要将对应区间结点找出后暴力求解即可。
CF896C的珂朵莉树板子
struct node{ int l, r; mutable LL v; node(int L, int R = -1, LL V = 0):l(L), r(R), v(V){} bool operator < (const node& o) const{ return l < o.l; } } set<node> s; vector<node> vp; set<node>::iterator split(int pos){ auto it = s.lower_bound(node(pos)); if(it != s.end() && it->l == pos) return it; --it; if(pos > it->r) return s.end(); int L = it->l, R = it->r; LL v = it->v; s.erase(it); s.insert(node(L, pos-1, v)); return s.insert(node(pos, R, v)).first; } void add(int l, int r, LL val = 1){ split(l); auto itr = split(r+1), itl = split(l); for(; itl != itr; ++itl) itl->v += val; } void assign(int l, int r, LL val = 0){ split(l); auto itr = split(r+1), itl = split(l); s.erase(itl, itr); s.insert(node(l, r, val)); } LL rank(int l, int r, int k, bool reversed = 0){ if(reversed) k = r - l + 2 - k; split(l); auto itr = split(r+1), itl = split(l); vp.clear(); for(; itl != itr; ++itl) vp.push_back({itl->v, itl->r - itl->l + 1}); sort(vp.begin(), vp.end()); for(auto i : vp){ k -= i.second; if(k <= 0) return i.first; } return -1; } LL ksm(LL b, LL p){ LL base = b; LL ans = 1; while(p){ if(p&1) ans *= base; base *= b; p >>= 1; } return ans; } LL sum(int l, int r, int ex, int mod){ split(l); auto itr = split(r+1), itl = split(l); LL res = 0; for(; itl != itr; ++itl) res = (res + (LL)(itl->r - itl->l + 1)*ksm(itl->v, ex) % mod); return res
}
&luogu上的一道可以用珂朵莉树A掉的题
https://www.luogu.com.cn/problem/P4979
Code:
#include<bits/stdc++.h> using namespace std; typedef long long LL; struct node{ int l, r; mutable char v; node(int L, int R = -1, char V = 0):l(L), r(R), v(V){} bool operator < (const node& o) const{ return l < o.l; } }; set<node> s; int n; set<node>::iterator split(int pos){ auto it = s.lower_bound(node(pos)); if(it != s.end() && it->l == pos) return it; --it; if(pos > it->r) return s.end(); int L = it->l, R = it->r; char v = it->v; s.erase(it); s.insert(node(L, pos-1, v)); return s.insert(node(pos, R, v)).first; } bool judge(int l, int r){ auto itr = split(r+1), itl = split(l); char v = itl->v; for(auto it = itl; it != itr; it++){ if(it->v != v) return false; } if(l == 1 || r == n) return true; itl--; if(itr->v != itl->v) return true; else return false; } void assign(int l, int r, char val){ //split(l); auto itr = split(r+1), itl = split(l); s.erase(itl, itr); s.insert(node(l, r, val)); } int main(){ ios::sync_with_stdio(false), cin.tie(0); int k; char c; int l, r; char op; string str; cin>>n>>str; for(int i = 0; i < str.size(); i++) s.insert(node(i+1, i+1, str[i])); cin>>k; while(k--){ char x; cin>>x>>l>>r; if(x == 'A'){ cin>>op; assign(l, r, op); } else if(judge(l, r)) cout<<"Yes\n"; else cout<<"No\n"; } return 0; }
&初始化的时候直接暴力初始化了
-O2 600MS 预先把结点初始化好会更快。