珂朵(塔)莉树
珂朵莉树
珂朵莉最珂爱了不接受反驳
好了我们进入正题......
一些必须知道的东西
- 珂朵莉树不是树!!!
- 珂朵莉树只是一种基于平衡树的数据结构.
- 珂朵莉树的使用需要两个条件
- 数据随机
- 有区间赋值操作
- 珂朵莉树适用范围并不是很大, 但是珂以解决一些奇怪的问题, 比如说:
- 珂朵莉树较好调试, 但是有时珂以把你RE出屎......
- 珂朵莉树的变种数量实在太多, 所以我们不会把它封装起来.
- 珂朵莉树是一种暴力数据结构
- 珂朵莉树很好调试
好了差不多就是这么多了, 大家看着办吧......
珂朵莉的结构
先看一道例题, 以CF896C为例.
我们要
- 区间加法
- 区间赋值
- 区间第x小
- 区间k次方和......
数据保证随机(随机数生成器都给你了你还说他不随机?)
咋一看是一道毒瘤, 但是一看第二个操作比较好, 从它入手
然后经过一些随机操作我们会发现这个数组不难看, 比如说:
1, 1, 5, 5, 5, 5, 12, 7, 7, 7, 15
如果创建一个数据结构, 使得它里面存储的是大量的区间, 那么我们每一次2号操作, [L,R]里面的区间就只剩一个了.
我们最后决定用平衡树来存储区间, 因为我们需要进行二分操作.
每一次操作时把需要区间最左右的元素所在的区间切开, 然后对中间的每一个区间操作.
最终我们得到了一个\(O(\sqrt{n})\)的数据结构.
使用stl::set
来代替一个手写的平衡树, 然后我们得到了这样一个东西:
class kotree {
class node {
public:
int l, r;
mutable long long val;// mutable 表示"不能为const", 因为iterator加上*运算符后会返回const值, 这里加上mutablr就<font color=#81B9F0>珂</font>以进行修改.
bool operator < (const node& ot)const {
return l < ot.l;
}//平衡树需要的比较操作
};
set<node>odt;
};
好了我们先来看最基本的操作: 切分, 把一个区间砍成两个
我们先找到要砍那个区间, 然后砍它就是了......
auto split(int x) {// 找到x所在的区间, 然后把它切成两半并返回右侧的迭代器(x在右面的那一半)
auto it = odt.lower_bound({ x,0,0 });// 寻找切分目标右面的那个
if (it != odt.end() && it->l == x)return it;// 如果寻找到的恰好左端点就是x那么无需切分, 直接返回即<font color=#81B9F0>珂</font>
it--;// x在左面的区间里
int l = it->l, r = it->r;
long long val = it->val;
odt.erase(it);
odt.insert({ l,x - 1,val });
return odt.insert({ x,r,val }).first; //insert的返回值包含两部分: first:迭代器 second: 成功与否
}
然后全部都是暴力操作啊......
void add(int l, int r, long long x) {
auto rb = split(r + 1), lb = split(l);
while (lb != rb)lb->val += x, lb++;//遍历区间的日常方式
}
void assign(int l, int r, long long x) {
auto rb = split(r + 1), lb = split(l);
odt.erase(lb, rb);//想不到吧一次全删
odt.insert({ l,r,x });
}
long long kth(int l, int r, int x) {
vector<mp>ygy;
auto rb = split(r + 1), lb = split(l);
while (lb != rb)ygy.push_back(mp(lb->val, lb->r - lb->l + 1)), lb++;
sort(ygy.begin(), ygy.end());
for (auto it : ygy) {
x -= it.second;
if (x <= 0)return it.first;
}
return -1;
}
long long sum(int l, int r, int k, long long m) {
auto rb = split(r + 1), lb = split(l);
long long ans = 0;
while (lb != rb)ans = (ans + 1ll * ksm(lb->val, k, m) * (1ll * lb->r - lb->l + 1) % m) % m, lb++;
return ans;
}
是的你没看错就是这么暴力......
珂朵莉: 我这么珂爱怎么会暴力呢?......
然后整棵树长成这样:
typedef pair<long long, int>mp;
class kotree {
class node {
public:
int l, r;
mutable long long val;
bool operator < (const node& ot)const {
return l < ot.l;
}
};
set<node>odt;
auto split(int x) {
auto it = odt.lower_bound({ x,0,0 });
if (it != odt.end() && it->l == x)return it;
it--;
int l = it->l, r = it->r;
long long val = it->val;
odt.erase(it);
odt.insert({ l,x - 1,val });
return odt.insert({ x,r,val }).first;
}
public:
kotree(int n = 0) {
odt.clear();
odt.insert({ 1,n,0 });
}
void add(int l, int r, long long x) {
auto rb = split(r + 1), lb = split(l);
while (lb != rb)lb->val += x, lb++;
}
void assign(int l, int r, long long x) {
auto rb = split(r + 1), lb = split(l);
odt.erase(lb, rb);
odt.insert({ l,r,x });
}
long long kth(int l, int r, int x) {
vector<mp>ygy;
auto rb = split(r + 1), lb = split(l);
while (lb != rb)ygy.push_back(mp(lb->val, lb->r - lb->l + 1)), lb++;
sort(ygy.begin(), ygy.end());
for (auto it : ygy) {
x -= it.second;
if (x <= 0)return it.first;
}
return -1;
}
long long sum(int l, int r, int k, long long m) {
auto rb = split(r + 1), lb = split(l);
long long ans = 0;
while (lb != rb)ans = (ans + 1ll * ksm(lb->val, k, m) * (1ll * lb->r - lb->l + 1) % m) % m, lb++;
return ans;
}
}ygy;
然后将题目主体(main)补充一下就珂以了...... 题目连伪代码都给你了......
总之代码如下:
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<algorithm>
#include<set>
using namespace std;
namespace fio {
streambuf* in = cin.rdbuf();
char bb[1000000], * s = bb, * t = bb;
#define gc() (s==t&&(t=(s=bb)+in->sgetn(bb,1000000),s==t)?EOF:*s++)
inline int read() {
int x = 0; char ch = gc();
while (ch < 48)ch = gc();
while (ch >= 48)x = x * 10 + ch - 48, ch = gc();
return x;
}
}using namespace fio;
long long ksm(long long a, long long b, long long k) {
long long ans = 1;
for (a %= k; b; a = a * a % k, b >>= 1)if (b & 1)ans = ans * a % k;
return ans;
}
typedef pair<long long, int>mp;
class kotree {
class node {
public:
int l, r;
mutable long long val;
bool operator < (const node& ot)const {
return l < ot.l;
}
};
set<node>odt;
auto split(int x) {
auto it = odt.lower_bound({ x,0,0 });
if (it != odt.end() && it->l == x)return it;
it--;
int l = it->l, r = it->r;
long long val = it->val;
odt.erase(it);
odt.insert({ l,x - 1,val });
return odt.insert({ x,r,val }).first;
}
public:
kotree(int n = 0) {
odt.clear();
odt.insert({ 1,n,0 });
}
void add(int l, int r, long long x) {
auto lb = split(l), rb = split(r + 1);
while (lb != rb)lb->val += x, lb++;
}
void assign(int l, int r, long long x) {
auto rb = split(r + 1), lb = split(l);
odt.erase(lb, rb);
odt.insert({ l,r,x });
}
long long kth(int l, int r, int x) {
vector<mp>ygy;
auto rb = split(r + 1), lb = split(l);
while (lb != rb)ygy.push_back(mp(lb->val, lb->r - lb->l + 1)), lb++;
sort(ygy.begin(), ygy.end());
for (auto it : ygy) {
x -= it.second;
if (x <= 0)return it.first;
}
return -1;
}
long long sum(int l, int r, int k, long long m) {
auto rb = split(r + 1), lb = split(l);
long long ans = 0;
while (lb != rb)ans = (ans + 1ll * ksm(lb->val, k, m) * (1ll * lb->r - lb->l + 1) % m) % m, lb++;
return ans;
}
}ygy;
int n, m, k, d;
class rng {
int seed, vmax;
public:
int rand(int mod = 4) {
int ret = seed;
seed = (1ll * seed * 7 + 13) % 1000000007;
return ret % mod;
}
rng(int seed = 0, int vmax = 0) :seed(seed), vmax(vmax) {}
}rnd;
int main() {
cin >> n >> m >> k >> d;
rnd = rng(k, d);
ygy = kotree(n);
for (int i = 1; i <= n; i++)ygy.assign(i, i, 1ll * rnd.rand(d) + 1);
for (int i = 1; i <= m; i++) {
int op = rnd.rand() + 1;
int l = rnd.rand(n) + 1, r = rnd.rand(n) + 1;
if (l > r)swap(l, r);
int x, y;
if (op == 3)x = rnd.rand(r - l + 1) + 1;
else x = rnd.rand(d) + 1;
if (op == 4)y = rnd.rand(d) + 1;
switch (op) {
case 1:
ygy.add(l, r, x);
break;
case 2:
ygy.assign(l, r, x);
break;
case 3:
cout << ygy.kth(l, r, x) << endl;
break;
case 4:
cout << ygy.sum(l, r, x, y) << endl;
break;
}
}
}
看完了, 注意以下几点:
- 一定要记住这个细节:
auto rb = split(r + 1), lb = split(l);
不然将你RE出屎就是这里! 想一想为什么.但是现在市面上大多数珂朵莉树都是这么写的天知道它们是怎么过的...... split
函数一定一定不要写错! 是整个珂朵莉树的根基函数.- 记住珂朵莉树的应用条件有数据随机一项......
我们再来看一道CF915E, 原则上要用动态开点线段树, 但是珂朵莉树也是随便秒...... 因为...... 所有操作都是区间赋值你想让我干什么?!
但是注意一下这么几点: 这一题不能直接区间赋值然后计算, 小心30点TLE掉.
计算的时候要加一个优化(求和)
嗯对差不多就是这样了.
#include"pch.h"
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<algorithm>
#include<set>
using namespace std;
namespace fio {
streambuf* in = cin.rdbuf();
char bb[1000000], * s = bb, * t = bb;
#define gc() (s==t&&(t=(s=bb)+in->sgetn(bb,1000000),s==t)?EOF:*s++)
inline int read() {
int x = 0; char ch = gc();
while (ch < 48)ch = gc();
while (ch >= 48)x = x * 10 + ch - 48, ch = gc();
return x;
}
}using fio::read;
int n, m, k;
class kotree {
protected:
class node {
public:
int l, r;
bool val;
bool operator < (const node ot)const {
return l < ot.l;
}
};
set<node>odt;
auto split(int x) {
auto nex = odt.lower_bound({ x,0,0 });
if (nex != odt.end() && nex->l == x)return nex;
nex--;
int l = nex->l, r = nex->r;
bool val = nex->val;
odt.erase(nex), odt.insert({ l,x - 1,val });
return odt.insert({ x,r,val }).first;
}
int ans;
public:
kotree(int n = 0) { odt.clear(), odt.insert({ 1,n,1 }), ans = n; }
void assign(int l, int r, bool val) {
auto rb = split(r + 1), lb = split(l);
for (auto it = lb; it != rb; it++)ans -= it->val * (it->r - it->l + 1);
ans += (r - l + 1) * val;
odt.erase(lb, rb), odt.insert({ l,r,val });
}
int total() { return ans; }
}ygy;
void w(int x) {
if (x > 9)w(x / 10);
putchar(x % 10 + 48);
}
int main() {
n = read(), m = read();
ygy = kotree(n);
for (int i = 1; i <= m; i++) {
int a = read(), b = read(), c = read();
ygy.assign(a, b, (c - 1) != 0);
w(ygy.total()), putchar('\n');
}
}
简单, 霸气, 快!
然而这是一种暴力(?)数据结构. 只能应对随机数据, 这样珂以有效地限制区间数量.
emmm就是这么多了......