珂学数据结构

1. 珂朵莉树

珂朵莉树(也称 Old Driver Tree),是一种暴力数据结构,其代表的操作有:

  • 区间推平(将区间 [l,r] 的数修改为 x);
  • 区间次幂和(给定 x,y,计算 liraixmody)。

当然珂朵莉树也可以做到区间加,区间第 x 小等操作。

珂朵莉树适用于数据随机的情况,平均时间复杂度 O(mlogn)。在数据不随机的情况下可能会被卡飞。

接下来以 CF896C Willem, Chtholly and Seniorious 为例,讲解珂朵莉树的实现方式。

1.1 初始化

珂朵莉树是以一个结构体 Node 为基础的,我们对于每一个区间,定义 l,r,v,分别表示区间左端点,区间右端点和区间中的值。

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 s 来存储这些区间,由于我们重载了运算符,所以 s 中的区间会按左端点从小到大排序。

1.2 Split 操作

珂朵莉树的关键操作就是 split,它可以将区间 [l,r] 划分为两个区间 [l,pos1][pos,r]

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,它可以将 [l,r] 合并为一个区间,并赋值为 x

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;
}

区间第 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. 奈芙莲树

P3934 [Ynoi2016] 炸脖龙

fl,r=alal+1ar,根据扩展欧拉定理,可以递归计算它的值。注意扩展欧拉定理需要分类讨论。

需要维护区间修改,单点查值,使用树状数组即可。

点击查看代码
#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. 瑟里欧尼斯树(第二分块)

P4117 [Ynoi2018] 五彩斑斓的世界

本文作者:Jasper08

本文链接:https://www.cnblogs.com/Jasper08/p/17012652.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Jasper08  阅读(126)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑