珂朵莉树学习笔记

0x00 前言

0x01 关于其命名

最开始出现在 Codeforces Round #449 (Div. 1) C题 上,这位珂学家在题解中用了一种玄学的数据结构解题,开始命名为 ODT树(Old Driver Tree,老司机树,以出题者的ID命名),后来普遍称为珂朵莉树。

0x02 能解决的问题

珂朵莉树用于解决含有区间平推操作(即将区间上的数全部变为一个数)的问题时卓有成效,在数据随机的情况下,用 set 实现复杂度为 (O(N \ log \ log \ N)),用链表实现复杂度为 (O(N \ log \ N)),比同类问题其他算法更优。时间复杂度证明请移步这篇文章

0x03 前置知识

0x10 正文

本文使用 Codeforces Round #449 (Div. 1) C题 作为例题讲解珂朵莉树。

0x11 题意

— 威廉…

— 怎么了?
— 瑟尼欧里斯好像出了什么问题…

— 我会看看的…

瑟尼欧里斯是一把由特殊护符按特定顺序排列组成的剑。
已经 (500) 年过去了,现在剑的状态很差,所以威廉决定检查一下。
瑟尼欧里斯由 (n) 片护符组成,威廉把它们排成一列,每个护符上有一个数字 (a_i)。
为了保养它,威廉需要进行 (m) 次操作。
这里有四种操作:

  • (1 \ l \ r \ x) : 将区间 ([l, r]) 上的数加上 (x)。
  • (2 \ l \ r \ x) : 将区间 ([l, r]) 上的数全部变为 (x)。
  • (3 \ l \ r \ x) : 查询区间 ([l, r]) 的第 (x) 大数。
  • (4 \ l \ r \ x \ y) : 查询区间 ([l, r]) 上的数的 (x) 次方之和对 (y) 取模的值。

本题输入较为特殊,输入格式如下:
一行四个整数,分别为 (n),(m),(seed),(vmax),前两个变量意义如题目所述,后两个变量用于生成随机数据,数据生成伪代码如下

def rnd():

    ret = seed
    seed = (seed * 7 + 13) mod 1000000007
    return ret

for i = 1 to n:

    a[i] = (rnd() mod vmax) + 1

for i = 1 to m:

    op = (rnd() mod 4) + 1
    l = (rnd() mod n) + 1
    r = (rnd() mod n) + 1

    if (l > r):
         swap(l, r)

    if (op == 3):
        x = (rnd() mod (r - l + 1)) + 1
    else:
        x = (rnd() mod vmax) + 1

    if (op == 4):
        y = (rnd() mod vmax) + 1

0x12 珂朵莉树基本思路

由于数据随机,所以在区间平推操作中区间长度普遍不会太短,所以区间总个数不会太多,于是我们就考虑维护每一个这样连续的区间,区间中的数都相同。

0x13 结构体定义

用一个结构体来维护每一个区间的信息。

struct node {
    ll l, r; //区间左右端点
    mutable ll v; //区间单个元素值
    node(ll l, ll r, ll v) : l(l), r(r), v(v) {}
    bool operator< (const node &a) const { return l < a.l; }
};

在上述定义中有下面一点需要注意:

  • 因为元素值并不是固定的,所以一定要用mutabel 让元素值可变起来

0x14 初始化

#include
set tree;

这样你就得到了一颗啥也没有的珂朵莉树。

0x15 spilt操作

因为一个区间上的数不一定自始至终都是一样的,所以我们需要一个分割函数将区间分隔开,这就是 spilt 函数。
这个操作是珂朵莉树的核心操作之一,此函数有一个参数,表示要分裂的位置,我们先看代码,再解释它的运作过程。

auto spilt(ll pos) {
    auto it = tree.lower_bound(node(pos, 0, 0));
    if(it != tree.end( ) && it -> l == pos) return it;
    it--;
    ll l = it -> l, r = it -> r, v = it -> v;
    tree.erase(it);
    tree.insert(node(l, pos - 1, v));
    return tree.insert(node(pos, r, v)).first;
}

首先,我们要找到一个左端点大于等于 (pos) 的区间,用一个迭代器指向它(注意,如果你使用的是c++11,auto 必须要换成 set

0x16 assgin操作

珂朵莉树的核心操作之二,也就是区间平推操作。
有了 spilt 函数,我们的实现也简单了很多,依旧是对着代码解释。

void assgin(ll l, ll r, ll v) {
    auto end = spilt(r + 1), start = spilt(l);
    tree.erase(start, end);
    tree.insert(node(l, r, v));
}

实现思路没什么好讲的,无非就是断开需要赋值的区间,全部删除再加入一个新的区间,重点在 spilt 的顺序上。
看上去貌似和顺序没什么关系,如果单从逻辑上看确实如此,但是如果从实现上去看就会发现问题。
假设我们要从区间 ([1, 10]) 里截取出 ([3, 7]),我们先执行 spilt(1),现在 start 迭代器指向的是区间 ([3, 10]),然后我们再执行 spilt(8),end 则指向了区间 ([8,10]),此时我们发现 start 指向的迭代器被第二次 spilt 操作 erase 掉了,所以调用时 可能会 RE。(之所以是可能,是因为这东西比较玄学,有可能一会 RE,一会 AC,为了避免这种麻烦,还是规范写法较为稳妥)
如果还是不理解,就结合下图再多看几遍上一段。

0x17 其他代码实现

核心代码就上面两个,剩下的乱搞就行。

void add(ll l, ll r, ll x) { //区间加操作
    auto end = spilt(r + 1), start = spilt(l);
    for(auto it = start; it != end; it++)
        it -> v += x; //mulable的作用在此
}
struct Rank {
    ll num, cnt; // 值与数量
    Rank(ll num, ll cnt) : num(num), cnt(cnt) {}
    bool operator< (const Rank &a) const { return num < a.num; }
};

ll get_rank(ll l, ll r, ll x) { //求区间第 x 大数
    auto end = spilt(r + 1), start = spilt(l);
    vector vec;
    for(auto it = start; it != end; it++) vec.push_back(Rank(it -> v, it -> r - it -> l + 1));
    sort(vec.begin( ), vec.end( )); //将区间上的所有数排序,以便后续暴力查找
    int i;
    for(i = 0; i < vec.size( ); i++) {
        if(vec[i].cnt < x) x -= vec[i].cnt;
        else break;
    }
    return vec[i].num;
}
ll get_power(ll l, ll r, ll x, ll y) { //求区间 x 次方和 mod y 的值
    auto end = spilt(r + 1), start = spilt(l);
    ll ans = 0;
    for(auto it = start; it != end; it++) ans = (ans + power(it -> v, x, y) * (it -> r - it -> l + 1) % y) % y; //power 为快速幂函数
    return ans;
}

0x17 完整代码

请在确保自己理解上述所有内容的情况下阅读

#include
#include
#include
#include
#include
using namespace std;

typedef long long ll;
const int MOD = 1e9 + 7;
ll n, m, seed, vmax;

struct node {
    ll l, r;
    mutable ll v;
    node(ll l, ll r, ll v) : l(l), r(r), v(v) {}
    bool operator< (const node &a) const { return l < a.l; }
};

struct Rank {
    ll num, cnt;
    Rank(ll num, ll cnt) : num(num), cnt(cnt) {}
    bool operator< (const Rank &a) const { return num < a.num; }
};

set tree;

ll rnd( );
auto split(ll pos);
void add(ll l, ll r, ll x);
ll power(ll a, ll b, ll p);
void assgin(ll l, ll r, ll v);
ll get_rank(ll l, ll r, ll x);
ll get_power(ll l, ll r, ll x, ll y);

int main( ) {
    cin >> n >> m >> seed >> vmax;
    for(int i = 1; i  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);
        if(op == 2) assgin(l, r, x);
        if(op == 3) cout << get_rank(l, r, x) << endl;
        if(op == 4) cout << get_power(l, r, x, y) << endl;
    }
    return 0;
}

auto spilt(ll pos) {
    auto it = tree.lower_bound(node(pos, 0, 0));
    if(it != tree.end( ) && it -> l == pos) return it;
    it--;
    ll l = it -> l, r = it -> r, v = it -> v;
    tree.erase(it);
    tree.insert(node(l, pos - 1, v));
    return tree.insert(node(pos, r, v)).first;
}

void assgin(ll l, ll r, ll v) {
    auto end = spilt(r + 1), start = spilt(l);
    tree.erase(start, end);
    tree.insert(node(l, r, v));
}

void add(ll l, ll r, ll x) {
    auto end = spilt(r + 1), start = spilt(l);
    for(auto it = start; it != end; it++)
        it -> v += x;
}

ll get_rank(ll l, ll r, ll x) {
    auto end = spilt(r + 1), start = spilt(l);
    vector vec;
    for(auto it = start; it != end; it++) vec.push_back(Rank(it -> v, it -> r - it -> l + 1));
    sort(vec.begin( ), vec.end( ));
    int i;
    for(i = 0; i < vec.size( ); i++) {
        if(vec[i].cnt < x) x -= vec[i].cnt;
        else break;
    }
    return vec[i].num;
}

ll get_power(ll l, ll r, ll x, ll y) {
    auto end = spilt(r + 1), start = spilt(l);
    ll ans = 0;
    for(auto it = start; it != end; it++) ans = (ans + power(it -> v, x, y) * (it -> r - it -> l + 1) % y) % y;
    return ans;
}

ll power(ll a, ll b, ll p) {
    ll res = 1, base = a % p;
    while(b) {
        if(b & 1) res = (res * base) % p;
        base = (base * base) % p;
        b >>= 1;
    }
    return res;
}

ll rnd( ) {
    ll res = seed;
    seed = (seed * 7 + 13) % MOD;
    return res;
}

0x18 小结

珂朵莉树的核心其实就二十行左右的代码,并不是什么很难的算法,但是由于其对于数据的要求,很少有题将其作为正解,但是考场骗分还是很有用的。

0x19 习题

0x20 后记

本文是本蒟蒻近期学习了珂朵莉树,为了巩固所以写下了这篇学习笔记,如果有纰漏请指出。
另外感谢本文用到的所有资料的提供者。
还有,珂朵莉太可爱了~

Original: https://www.cnblogs.com/PlankBlack/p/ChthollyTree.html
Author: plankblack
Title: 珂朵莉树学习笔记

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/800336/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • python中的with关键字

    with关键字用于管理 _不受控_的资源,就是需要我们在使用完后及时关闭的资源,比如文件流。这是python设计的语法糖,用于替代 try-finally语句,确保即使发生了异常,…

    Python 2023年6月12日
    057
  • 电影数据分析——国产烂片深度揭秘

    1 读取数据,以”豆瓣评分”为标准,看看电影评分分布,及烂片情况 要求: ① 读取数据”moviedata.xlsx”,去除缺失值 …

    Python 2023年8月20日
    034
  • 机器学习实战-AdaBoost

    1.概念 从若学习算法出发,反复学恶习得到一系列弱分类器(又称基本分类器),然后组合这些弱分类器构成一个强分类器。简单说就是假如有一堆数据data,不管是采用逻辑回归还是SVM算法…

    Python 2023年10月31日
    017
  • 使用pygame制作贪吃蛇小游戏

    使用pygame制作贪吃蛇小游戏 开发基本思路 * 效果展示 具体实施步骤 * 制作窗口,插入音频与图片 绘制蛇与果实 按键控制 生成食物 死亡设置 其他设置 整体代码 开发基本思…

    Python 2023年9月20日
    040
  • 【Python】matplotlib绘图的相关设置

    文章目录 * – 支持中文 – 在Jupyter Notebook中显示清晰的图像 – 画图风格设置 – Marker和Linest…

    Python 2023年9月7日
    049
  • 【计算机视觉】图像增强——图像的形态学操作

    个人简介: 📦个人主页:赵四司机🏆学习方向:JAVA后端开发⏰往期文章:SpringBoot项目整合微信支付🔔博主推荐网站:牛客网 刷题|面试|找工作神器📣种一棵树最好的时间是十年…

    Python 2023年9月27日
    053
  • matplotlib中条形图(柱状图)的简单使用及美化

    世界电影票房前十名统计图 世界电影票房前十名的数据如下: 阿凡达 : 28.47 亿美元 复仇者联盟4:终局之战 : 27.97 亿美元 泰坦尼克号 : 22.02 亿美元 星球大…

    Python 2023年8月31日
    069
  • 玩转Python飞机大战项目

    文章目录 前言 一、代码下载及导入项目 二、安装相关依赖组件 * 1.安装pygame 2.安装pyinstaller 三、运行及打包 * 1、运行 2、打包成可执行文件。 总结 …

    Python 2023年9月22日
    044
  • NumPy基础

    先贴个链接 numpy官方函数文档 numpy函数文档民间汉化 numpy最大的特点是n维数组对象ndarray,它可以容纳各种类型数据,是最重要的操作对象 创建 以下代码使用ar…

    Python 2023年8月26日
    050
  • 机器学习基本库之Matplotlib

    Matplotlib是机器学习中一个非常重要进行可视化的库,能够将复杂的数据以图表的形式显示出来,非常的清晰直观。下面让我们来看一下它的用法。 import pandas as p…

    Python 2023年9月6日
    045
  • 【Pygame实战】代码版《舞动青春*炫舞》能否引领音舞游戏再一次爆发?“你还记得最浪漫的舞蹈游戏炫舞吗?”

    导语 Hello,大家好呀!我是木木子吖~ 一个集美貌幽默风趣善良可爱并努力码代码的程序媛一枚。 听说关注我的人会一夜暴富发大财哦~ (哇哇哇 这真的爱😍😍) 所有文章完整的素材+…

    Python 2023年9月19日
    056
  • CPU流水线与指令乱序执行

    青蛙见了蜈蚣,好奇地问:”蜈蚣大哥,我很好奇,你那么多条腿,走路的时候先迈哪一条啊?” 蜈蚣听后说:”青蛙老弟,我一直就这么走路,从没想过先迈哪…

    Python 2023年10月23日
    032
  • 1.Flask配置文件

    1. 配置文件 1.1 介绍 from flask import Flask app = Flask(__name__) print(app.config) flask中的配置文件…

    Python 2023年8月12日
    044
  • pytest:如何调用 pytest

    指定要运行的测试 pytest 支持多种从命令行运行和选择测试的方法。 模块中运行测试( .py文件 ) pytest sample_class_test.py 在目录中运行测试 …

    Python 2023年9月12日
    047
  • Pandas 横向数据汇总实例

    在某些情况下需要对Excel中的数据做横向汇总,此时使用Pandas的将体现出很强的优势,请看下面的数据: 表格中有5个子类别:类别1—-类别5,每一行中至少有1个类别…

    Python 2023年8月21日
    028
  • pytest的mark使用

    @pytest.mark标签使用 1. @pytest.mark.skip(reason=”)跳过运行 @pytest.mark.skip(reason="r…

    Python 2023年9月13日
    044
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球