珂学数据结构
1. 珂朵莉树
珂朵莉树(也称 Old Driver Tree),是一种暴力数据结构,其代表的操作有:
- 区间推平(将区间
的数修改为 ); - 区间次幂和(给定
,计算 )。
当然珂朵莉树也可以做到区间加,区间第
珂朵莉树适用于数据随机的情况,平均时间复杂度
接下来以 CF896C Willem, Chtholly and Seniorious 为例,讲解珂朵莉树的实现方式。
1.1 初始化
珂朵莉树是以一个结构体 Node
为基础的,我们对于每一个区间,定义
struct Node {
int l, r; // 左右端点
mutable int v; // 区间中的值, mutable表示v虽然是常量,但是可修改
Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {} // 成员函数,创造一个为(l,r,v)的区间
bool operator < (const Node &a) const { // 重载<运算符, 便于排序
return l < a.l;
}
};
接下来我们需要用一个 set
1.2 Split 操作
珂朵莉树的关键操作就是 split
,它可以将区间
set<Node>::iterator split(int pos) { // 分割操作
set<Node>::iterator it = s.lower_bound(Node(pos)); // 找到第一个左端点不小于pos的区间
if (it != s.end() && it->l == pos) return it; // pos本身就是区间左端点,直接返回
it --;
if (it->r < pos) return s.end(); //pos过大,超出了最后一个区间的范围
int l = it->l, r = it->r, v = it->v;
s.erase(it), s.insert(Node(l, pos-1, v)); // 将区间[l,r]分为两段[l,pos-1]和[pos,r]
return s.insert(Node(pos, r, v)).first; // 返回后一个区间的迭代器
}
1.3 Assign 操作
珂朵莉树不仅需要划分操作,还需要合并操作 assign
,它可以将
void assign(int l, int r, int x) { // 合并区间[l,r],并赋值为x
set<Node>::iterator itr = split(r+1), itl = split(l); // itr为r所在的区间的后一个区间的迭代器, itl为l所在的区间的迭代器
s.erase(itl, itr); // 删去itl~itr中的所有区间
s.insert(Node(l, r, x)); // 插入一个区间, 左端点为l, 右端点为r, 区间值为x
}
1.4 其他操作
其他操作基本都和 assign
操作差不多。
区间和:
void add(int l, int r, int x) { // 区间加
set<Node>::iterator itr = split(r+1), itl = split(l);
for (set<Node>::iterator it = itl; it != itr; ++it)
it->v += x;
}
区间第
struct Rank { // 定义一个结构体,存储每一个数的数值和数量
int num, cnt;
Rank (int num, int cnt) : num(num), cnt(cnt) {}
bool operator < (const Rank &a) const {
return num < a.num;
}
};
int rnk(int l, int r, int x) { // 查询区间第x小
set<Node>::iterator itr = split(r+1), itl = split(l);
vector<Rank> v;
for (set<Node>::iterator it = itl; it != itr; ++it)
v.push_back(Rank(it->v, it->r - it->l + 1)); // 一个区间[l,r]中, 有r-l+1个v, 将其插入vector
sort(v.begin(), v.end()); // 将v中的区间按数值排序
int i = 0;
for ( ; i < v.size(); ++i) {
if (v[i].cnt < x) {
x -= v[i].cnt;
} else {
break;
}
}
return v[i].num;
}
区间次幂和:
int pow(int a, int b, int p) {
int ans = 1;
a %= p;
while (b) {
if (b & 1) ans = ans * a % p;
a = a * a % p;
b >>= 1;
}
return ans;
}
int cal(int l, int r, int x, int y) { // 计算区间x次幂
set<Node>::iterator itr = split(r+1), itl = split(l);
int ans = 0;
for (set<Node>::iterator it = itl; it != itr; ++it)
ans = (ans + pow(it->v, x, y)*(it->r - it->l + 1) % y) % y;
return ans;
}
1.5 完整代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;
#define int long long
const int mod = 1e9+7;
const int N = 1e5+10;
int n, m, seed, vmax;
int a[N];
int rnd() {
int ret = seed;
seed = (seed * 7ll + 13) % mod;
return ret;
}
struct Node {
int l, r;
mutable int v;
Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}
bool operator < (const Node &a) const {
return l < a.l;
}
};
set<Node> s;
set<Node>::iterator split(int pos) { // 分割操作
set<Node>::iterator it = s.lower_bound(Node(pos));
if (it != s.end() && it->l == pos) return it; // pos本身就是区间左端点
it --;
if (it->r < pos) return s.end(); //pos过大
int l = it->l, r = it->r, v = it->v;
s.erase(it), s.insert(Node(l, pos-1, v)); // 将区间[l,r]分为两段[l,pos-1]和[pos,r]
return s.insert(Node(pos, r, v)).first; // 返回后一个区间的迭代器
}
void assign(int l, int r, int x) { // 合并两个区间,并赋值为x
set<Node>::iterator itr = split(r+1), itl = split(l);
s.erase(itl, itr);
s.insert(Node(l, r, x));
}
void add(int l, int r, int x) { // 区间加
set<Node>::iterator itr = split(r+1), itl = split(l);
for (set<Node>::iterator it = itl; it != itr; ++it)
it->v += x;
}
struct Rank {
int num, cnt;
Rank (int num, int cnt) : num(num), cnt(cnt) {}
bool operator < (const Rank &a) const {
return num < a.num;
}
};
int rnk(int l, int r, int x) { // 查询区间第x小
set<Node>::iterator itr = split(r+1), itl = split(l);
vector<Rank> v;
for (set<Node>::iterator it = itl; it != itr; ++it)
v.push_back(Rank(it->v, it->r - it->l + 1));
sort(v.begin(), v.end());
int i = 0;
for ( ; i < v.size(); ++i) {
if (v[i].cnt < x) {
x -= v[i].cnt;
} else {
break;
}
}
return v[i].num;
}
int pow(int a, int b, int p) {
int ans = 1;
a %= p;
while (b) {
if (b & 1) ans = ans * a % p;
a = a * a % p;
b >>= 1;
}
return ans;
}
int cal(int l, int r, int x, int y) { // 计算区间x次幂
set<Node>::iterator itr = split(r+1), itl = split(l);
int ans = 0;
for (set<Node>::iterator it = itl; it != itr; ++it)
ans = (ans + pow(it->v, x, y)*(it->r - it->l + 1) % y) % y;
return ans;
}
signed main() {
scanf("%lld%lld%lld%lld", &n, &m, &seed, &vmax);
for (int i = 1; i <= n; ++i) a[i] = (rnd() % vmax) + 1, s.insert(Node(i, i, a[i]));
int op, l, r, x, y;
for (int i = 1; i <= m; ++i) {
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;
if (op == 1) {
add(l, r, x);
} else if (op == 2) {
assign(l, r, x);
} else if (op == 3) {
printf("%lld\n", rnk(l, r, x));
} else {
printf("%lld\n", cal(l, r, x, y));
}
}
system("pause");
return 0;
}
例题:
CF915E Physical Education Lessons
P4979 矿洞:坍塌 颜色合并优化。
P5350 序列 注意什么时候用 split
操作,以及迭代器是否发生改变。
P5251 [LnOI2019]第二代图灵机 珂朵莉树+线段树,线段树维护区间和,珂朵莉树上双指针。
2. 奈芙莲树
令
需要维护区间修改,单点查值,使用树状数组即可。
点击查看代码
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
#define int long long
const int N = 5e5+10, M = 2e7+10;
int n, m, c[N];
int cnt, prime[M], phi[M]; bool st[M];
void Euler(int n) {
st[0] = st[1] = 1, phi[0] = phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!st[i]) prime[++ cnt] = i, phi[i] = i-1;
for (int j = 1; prime[j] <= n/i && j <= cnt; ++j) {
st[i*prime[j]] = 1;
if (i % prime[j] == 0) {
phi[i*prime[j]] = phi[i] * prime[j];
break;
}
phi[i*prime[j]] = phi[i] * (prime[j]-1);
}
}
}
int lowbit(int x) {
return x & -x;
}
void add(int pos, int x) {
for (int i = pos; i <= n; i += lowbit(i))
c[i] += x;
}
int query(int pos) {
int sum = 0;
for (int i = pos; i >= 1; i -= lowbit(i))
sum += c[i];
return sum;
}
struct Node {
int v; bool flag; // v存储值, flag存储是否>=p
Node (int v = 0, bool flag = false) : v(v), flag(flag) {}
};
Node power(int a, int b, int p) { // 在快速幂的过程中判断是否>=p
Node res = Node(1, 0);
if (a >= p) a %= p, res.flag = 1;
while (b) {
if (b & 1) res.v *= a;
if (res.v >= p) res.flag = 1, res.v %= p;
a *= a;
if (a >= p) res.flag = 1, a %= p;
b >>= 1;
}
return res;
}
Node solve(int l, int r, int p) {
int L = query(l);
if (p == 1) return Node(0, 1);
if (L == 1) return Node(1, 0);
if (l == r) return (L<p) ? Node(L, 0) : Node(L%p, 1);
Node res = solve(l+1, r, phi[p]);
if (res.flag) res.v += phi[p]; // 如果>=p,根据扩展欧拉定理指数还需要加上phi[p]
return power(L, res.v, p);
}
signed main() {
Euler((int)2e7);
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; ++i) {
int x; scanf("%lld", &x);
add(i, x), add(i+1, -x);
}
int op, l, r, x;
while (m -- ) {
scanf("%lld%lld%lld%lld", &op, &l, &r, &x);
if (op == 1) add(l, x), add(r+1, -x);
else printf("%lld\n", solve(l, r, x).v);
}
return 0;
}
3. 瑟里欧尼斯树(第二分块)
本文作者:Jasper08
本文链接:https://www.cnblogs.com/Jasper08/p/17012652.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步