基于链表实现的珂朵莉树

浅谈珂朵莉树 珂朵莉树,超详细! 阅读详情

以下内容基于 CF896C Willem, Chtholly and Seniorious 来介绍。

珂朵莉树实质上是一种可以维护区间上的分裂与合并的数据结构,但要求数据是随机的,或者有大量的随机合并操作,这样才能保证维护的区间个数是一个很小的值。

一开始,我们用不同的节点表示 [ 1 , 1 ] , [ 2 , 2 ] , . . . , [ n , n ] [1,1],[2,2],...,[n,n] [1,1],[2,2],...,[n,n] 以及该区间上的值。

本题中的“把区间 [ l , r ] [l,r] [l,r] 赋值为 x x x”对应着一个合并操作,若随机到的 [ l , r ] [l,r] [l,r] 范围比较大,则意味着有大量的节点会合并成一个节点。经测试,在若干次随机合并后,区间个数会骤降至一个稳定的范围(大约几十个),这是理解珂朵莉树的关键。

图例:横轴为操作次数,纵轴为区间个数

数据定义

目前主流的实现是基于 set 来维护节点,但由于平均维护的区间个数很小,set 的优势并不明显。相比之下,链表(或数组)能更简洁地维护分裂与合并操作。

typedef long long int64;

struct Block {
    Block *next; // 链表下一节点
    int l, r; // 区间范围
    int64 val; // 区间上的值

    Block(Block *next, int l, int r, int64 val): next(next), l(l), r(r), val(val) {}
    bool operator<(const Block &b) const { return val < b.val; }
} *root;

基本操作

分裂区间

void split(int mid) {
	// 遍历链表
    for (Block *b = root; b; b = b->next) {
    	// 寻找能包含 mid 和 mid+1 的 [l, r],将其被拆分成 [l, mid] 和 [mid+1, r]
        if (b->l <= mid && mid + 1 <= b->r) { 
            b->next = new Block(b->next, mid + 1, b->r, b->val);
            b->r = mid;
            break;
        }
    }
}

在操作区间时,由于不能只维护区间的一部分,所以下面的操作进行之前都需要预先分裂区间,再完成相应操作。

// 预分裂,保证后续操作在 [l, r] 内部
void prepare(int l, int r) {
    split(l - 1);
    split(r);
}

合并区间

void merge(int l, int r, int64 val) {
	prepare(l, r)
	// 遍历链表
    for (Block *b = root; b; b = b->next) {
    	// 寻找区间左端点
        if (b->l == l) {
        	// 将区间 [b.l, b.r] 修改成 [b.l, r]
            b->r = r;
            b->val = val;
            // 然后寻找与 [b.l, r] 右侧相邻的区间,将当前节点链至该区间
            Block *tmp = b->next;
            while (tmp && tmp->l <= r) tmp = tmp->next;
            b->next = tmp;
            break;
        }
    }
}
// 注:这里没有释放被删除节点的内存,若有需要可自行添加

区间修改与计算

由于数据量很小,枚举整个链表即可。

// 区间更新
void add(int l, int r, int64 val) {
	prepare(l, r)
    for (Block *b = root; b; b = b->next) {
        if (l <= b->l && b->r <= r) b->val += val;
    }
}

// 区间第 k 小
int64 kth(int l, int r, int k) {
	prepare(l, r)
    vector<Block> blocks;
    for (Block *b = root; b; b = b->next) {
        if (l <= b->l && b->r <= r) {
            blocks.emplace_back(*b);
        }
    }
    sort(blocks.begin(), blocks.end());
    k--;
    for (Block b: blocks) {
        int cnt = b.r - b.l + 1;
        if (k >= cnt) k -= cnt;
        else return b.val;
    }
}

// 快速幂
int64 quick_pow(int64 x, int n, int64 mod) {
    x %= mod;
    int64 res = 1 % mod;
    for (; n; n >>= 1) {
        if (n & 1) res = res * x % mod;
        x = x * x % mod;
    }
    return res;
}

// 区间幂和
int64 pow_sum(int l, int r, int n, int64 mod) {
	prepare(l, r)
    int64 sum = 0;
    for (Block *b = root; b; b = b->next) {
        if (l <= b->l && b->r <= r) {
            sum += int64(b->r - b->l + 1) * quick_pow(b->val, n, mod);
        }
    }
    return sum % mod;
}

AC 代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
typedef long long int64;

int64 seed;

// 生成 [0, n-1] 的随机数
int rand(int n) {
    int64 ret = seed;
    seed = (seed * 7 + 13) % int64(1e9 + 7);
    return int(ret) % n;
}

struct Block {
    Block *next;
    int l, r;
    int64 val;

    Block(Block *next, int l, int r, int64 val): next(next), l(l), r(r), val(val) {}
    bool operator<(const Block &b) const { return val < b.val; }
} *root;

void split(int mid) {
    for (Block *b = root; b; b = b->next) {
        if (b->l <= mid && mid + 1 <= b->r) {
            b->next = new Block(b->next, mid + 1, b->r, b->val);
            b->r = mid;
            break;
        }
    }
}

void prepare(int l, int r) {
    split(l - 1);
    split(r);
}

void add(int l, int r, int64 val) {
    for (Block *b = root; b; b = b->next) {
        if (l <= b->l && b->r <= r) b->val += val;
    }
}

void merge(int l, int r, int64 val) {
    for (Block *b = root; b; b = b->next) {
        if (b->l == l) {
            b->r = r;
            b->val = val;
            Block *tmp = b->next;
            while (tmp && tmp->l <= r) tmp = tmp->next;
            b->next = tmp;
            break;
        }
    }
}

int64 kth(int l, int r, int k) {
    vector<Block> blocks;
    for (Block *b = root; b; b = b->next) {
        if (l <= b->l && b->r <= r) {
            blocks.emplace_back(*b);
        }
    }
    sort(blocks.begin(), blocks.end());
    k--;
    for (Block b: blocks) {
        int cnt = b.r - b.l + 1;
        if (k >= cnt) k -= cnt;
        else return b.val;
    }
}

int64 quick_pow(int64 x, int n, int64 mod) {
    x %= mod;
    int64 res = 1 % mod;
    for (; n; n >>= 1) {
        if (n & 1) res = res * x % mod;
        x = x * x % mod;
    }
    return res;
}

int64 pow_sum(int l, int r, int n, int64 mod) {
    int64 sum = 0;
    for (Block *b = root; b; b = b->next) {
        if (l <= b->l && b->r <= r) {
            sum += int64(b->r - b->l + 1) * quick_pow(b->val, n, mod);
        }
    }
    return sum % mod;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m, v_max;
    cin >> n >> m >> seed >> v_max;

    root = new Block(nullptr, 0, 0, rand(v_max) + 1);
    Block *b = root;
    for (int i = 1; i < n; i++) {
        b->next = new Block(nullptr, i, i, rand(v_max) + 1);
        b = b->next;
    }

    for (; m; m--) {
        int op = rand(4) + 1;
        int l = rand(n), r = rand(n);
        if (l > r) swap(l, r);
        int x = op == 3 ? rand(r - l + 1) + 1 : rand(v_max) + 1;
        prepare(l, r); // 在所有操作前预分裂
        switch (op) {
            case 1:
                add(l, r, x);
                break;
            case 2:
                merge(l, r, x);
                break;
            case 3:
                cout << kth(l, r, x) << '\n';
                break;
            default:
                int64 y = rand(v_max) + 1;
                cout << pow_sum(l, r, x, y) << '\n';
                break;
        }
    }
    return 0;
}
CSP-J模拟赛四补题报告 操作1: 让结点 u 的权值增加 k ,并对与结点 u 相邻的结点中,深度比结点 k 小的结点重复操作1。操作2:让结点 u 的权值增加 k ,并对与结点 u 相邻的结点中,深度比结点 u 大的结点重复操作2。对于每一组数据:有n个字符串(可能是单个字符),ta们是“非法的”,现给定一个大字符串,这些“非法的”小字符串不能出现在大字符串中(对于长度>=2的非法字符串,在大字符串中出现一部分被认为是合法的。(字符越靠后,其他非法字符串含有这个字符的可能性越高)当然,代码很少,时间复杂度很高(O(n^3)) 阅读详情

相关推荐

【CSP-J 2021】 小熊的果篮

【CSP-J 2021】 小熊的果篮 解题报告

weixin_60849605的博客 1685

浅谈珂朵莉树(Chtholly Tree)——暴力而玄学的数据结构

关于它很珂学的名字… 珂朵莉树(Chtholly Tree),又称老司机树(Old Driver Tree),起源于CodeFoeces平台上编号为896C的一道题—— “ Willem, Chtholly and Seniorious ” (珂学家们此时不用翻译也知道这个名字是在说啥了) ,一位用户Old Driver在给出了线段树的正解之后,又发布了YY多年的 一份前所未有的玄学解法,其中利用......

CC_dsm的博客 2万+

从RRT到平滑轨迹:机械臂避障规划仿真全流程解析

本文详细解析了机械臂避障规划从RRT算法到平滑轨迹优化的全流程,包括MATLAB实现、碰撞检测工程实践及轨迹平滑优化技巧。通过RRT算法在关节空间的路径搜索与Minimum Jerk/Snap优化,实现机械臂在复杂环境中的高效安全运动,适用于工业自动化与机器人控制领域。

weixin_29051811的博客 294

珂朵莉树(ODT)

珂朵莉树 ODT 主要内容 珂朵莉树是基于数据随机且有整体赋值操作而对序列操作的乱搞算法。 它的主要思想是用 set 维护若干个数值上相同的区间,并暴力处理其他询问。 建立 在 set 中,我们需要用结构体记录每个区间的信息: struct NODE { int l,r; mutable ll val; // mutable 表示可变的,便于在外边修改 val 的值 NODE(int...

EricQian的博客 3044

PLC编程标准之争:IEC61131与IEC61499核心区别与选型指南

在工业自动化领域,PLC编程长期依赖IEC61131标准,它定义了梯形图、结构化文本等语言,通过周期扫描机制保证确定性执行。然而,随着工业4.0和分布式控制场景的兴起,IEC61499以事件驱动模型打破传统范式,让功能块能跨设备协同,实现更灵活的柔性产线和模块化系统。理解两者在程序结构、数据交互、部署方式和实时性上的差异,是选型与工程落地的关键。本文从基础概念出发,剖析执行模型与架构演进,结合一线调试经验,帮助工程师在传统PLC与下一代标准之间做出理性决策。

weixin_27014595的博客 268

【UE5】虚幻引擎Gameplay框架

游戏实例(UGameInstance)会在引擎启动时实例化,并保持活跃直至引擎关闭。这是一种管理器类,在游戏中不存在实体,但会追踪数据和运行代码。在联网多人游戏中,游戏实例不会被复制,而是独立存在于服务器以及所有互联的客户端上。所有你希望在关卡加载间保持的内容都应存于游戏实例中。例如,游戏实例是管理游戏保存系统的到地方。游戏实例还可以充当任意数量的 游戏实例子系统 的管理器,这些子系统由游戏实例创建和销毁,并且与游戏实例本身具有相同的生命周期。在线子系统就属于这类子系统。

一个写程序的 1795

【算法】【C++、珂朵莉树】珂朵莉树,以力扣715. Range 模块为例

对小于运算符的重载,保证了节点在set中的有序性在声明变量val,也就是区间的值的时候,也要声明mutable,否则可能会CE,因为要进行区间赋值操作。int le, ri;本文详细介绍了如何使用珂朵莉树解决力扣715号问题,包括数据结构的设计思路和每个方法的实现步骤。通过合理的数据结构选择和高效的区间操作,我们实现了一个能够有效跟踪数字范围的Range模块。

未来就在脚下。 1509

生信入门第七课:使用FastQC软件进行测序原始fastq数据质控

从实验到数据:RNA定量、文库片段长度、fastqc质控和fastp去接头

微生信 489

珂朵莉树详解

珂朵莉树的起源? 珂朵莉树原名老司机树(Old Driver Tree,ODT),由2017年一场CF比赛中提出的数据结构,因为题目背景主角是《末日时在做什么?有没有空?可以来拯救吗?》的主角珂朵莉,因此该数据结构被称为珂朵莉树。 什么是珂朵莉树? 珂朵莉树是一种以近乎暴力的形式存储区间信息的一个数据结构。方式是通过set存放若干个用结构体表示的区间,每个区间的元素都是相同的。 珂朵莉树的用途? 只要是涉及到区间赋值操作的题,就可以用珂朵莉树处理几乎任何关于区间信息的询问 什么情况下可以用珂朵莉树而不被卡?

Suryxin's blogs 1万+

基于Simulink环境,提供一个关于如何对EMI抑制的DC-DC变换器进行仿真建模的示例

EMI定义:电磁干扰是指任何能中断、阻碍、降低或限制电气设备有效性能的电磁能量。重要性合规性:确保产品符合国际和地方的EMC标准。可靠性:减少因外部或内部EMI导致的系统故障风险。挑战滤波设计:合理设计输入输出滤波器以抑制传导EMI。布局与接地:良好的PCB设计对于减少辐射EMI至关重要。屏蔽:使用金属屏蔽来保护敏感电路不受外界干扰。首先明确要模拟的任务内容。设计一个能够在高频操作下有效抑制EMI的DC-DC变换器。根据工作频率和谐振元件值确定合适的滤波器参数。

xiaoheshang_123的博客 146

《QT+PCL 第一章》:点云读取显示

点云读取显示一、核心代码二、QT设置三、效果展示 一、核心代码 二、QT设置 三、效果展示

阁楼 1万+

【算法竞赛学习笔记】用不着数据结构之珂朵莉树

title : 珂朵莉树 date : 2021-8-26 tags : ACM,数据结构 author :Linno 简介 珂朵莉树(Chthlly Tree)也叫老司机树,ODT(Old Driver Tree)。 只要是由区间赋值操作的数据结构都可以使用这种数据结构来骗分,在数据随机的情况下效率很高。对于add,assign和sum操作,用set实现的珂朵莉树复杂度位O(nloglogn)。 问题起源 [CF 896C]n个数m次操作: ①区间加一个数、 ②区间赋值 ③求区间第k小 ④求区间幂次和.

SC_Linno的博客 512

Allegro如何使用Cross Copy命令快速复制器件的位号和丝印外形其他层

在Allegro做PCB设计的时候,如果需要复制器件的位号到其它层是无法直接实现的,如果直接拷贝器件的位号的话,效果如下。但是Allegro的Cross Copy命令是支持复制任何属性的图形到其它层的,下面以复制器件位号和丝印外框为例说明。同样如果使用Z-copy命令,也是无法Z-copy text的,Finds下方texts是灰色的。如何使用Cross Copy命令快速复制器件的位号和丝印外形其他层。另外cap处可以选择线段的端点是圆形,八角形,或者方形的铜皮。拷贝C1013,出现的是C*

weixin_54787054的博客 2671

珂朵莉树学习笔记

最开始出现在Codeforces Round #449 (Div. 1) C题上,这位珂学家在题解中用了一种玄学的数据结构解题,开始命名为 ODT树(Old Driver Tree,老司机树,以出题者的ID命名),后来普遍称为珂朵莉树。用一个结构体来维护每一个区间的信息。ll l, r;//区间左右端点//区间单个元素值}};因为元素值并不是固定的,所以一定要用mutabel让元素值可变起来。

m0_56069948的博客 618

(代码可运行)半导体器件和工艺模拟 使用Silvaco设计构建NMOS并提取各项工艺及器件参数

win10。

qq_20388971的博客 109

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

【2019.9.3】Za

上午:考试 下午: 扫描线学习宣布死亡QAQ 下次再说....... <summary>暂存代码</summary> yyb的#include<iostream> include include include include include include include include include using namespace std; d...

weixin_30338461的博客 164

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

珂朵莉树/颜色段均摊

简洁明了地介绍了珂朵莉树算法,可以高效解决区间问题。

OIer_wst的博客 2183

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

珂朵莉树实战指南

珂朵莉树摘要 珂朵莉树(ODT)是一种基于暴力思想的区间处理技巧,通过合并值相同的区间来高效处理多种区间操作。其核心函数包括: 区间断开:在指定位置分割区间 区间合并:将指定区间赋为统一值并合并 区间操作:暴力遍历每个区间节点单独处理 ODT适用于含区间推平操作的问题,在随机操作下均摊复杂度为O(m log n)。虽然暴力,但能灵活处理求和、最值、第k小等复杂区间问题,是骗分利器。 (字数:150) 相信珂学!暴力即是正义!

justskr2的博客 1043

珂朵莉树

概念 珂朵莉树(ODT),适用于有区间赋值操作且数据随机的题目。 珂朵莉树是一种“基于数据随机的颜色段均摊”,通过 set 维护区间。 其复杂度依赖于 assign 操作。 因此,题目中必须含有区间赋值。 同时,珂朵莉树依赖于数据随机。 在随机数据下, 使用 set 实现的珂朵莉树可以达到 O(nlog⁡log⁡n)\mathcal O(n\log \log n)O(nloglogn) 的复杂度(参见 这篇文章)。 思想 珂朵莉树的思想在于随机数据下的区间赋值操作很可能让大量元素变为同一个数。所以我们以三元

orz 1237

珂朵莉树/ODT 学习笔记

其实就是权值等于。

STJqwq的博客 543

珂朵莉树(老司机树,ODT,颜色段均摊)

前言 在宿舍里有人说珂朵莉树写起来比shi山线段树方便多了。 正文 珂朵莉树,又名老司机树,颜色段均摊,ODT。 可以在数据完全随机化的情况下较快的完成一些操作(所以容易被卡)。 珂朵莉树其实形态并不像树,基于set或是链表,是一种很暴力的数据结构。 先来看一到例题(珂朵莉树起源题) CF896C

cmdos的专栏 160

2022杭电多校5题解报告(同步自语雀)

2022杭电多校5题解报告(同步自语雀)

vermouth_1412的博客 391

内存分配 AC记:做题还是要细心

这道题一看上去就让人感觉很懵 索性就自己YY吧 看到“区间”两个字,我脑子里只有一个算法:珂朵莉树 那么我们开一个set,尝试着把每个可用的区间放进去,以左端点排序,再开一个优先队列,把当前正在执行的任务放进去,按照结束时间排序。 每个任务进来时,我们检索一遍占用优先队列,如果有已经结束的任务,就将它们退出,同时释放内存。(1) 然后检索内存区块set,检测是否有符合的区块,如果没有,就记录它进入...

MZW_BG的博客 326

题解:luogu P5568 [SDOI2008]校门外的区间

题解:luogu P5568 [SDOI2008]校门外的区间 luogu P5568 [SDOI2008]校门外的区间 前置知识:珂朵莉树 问题一:开闭区间 区间端点均为整数,不妨认为(lll,rrr)为(l+0.5l+0.5l+0.5,r−0.5r-0.5r−0.5) 乘2就可以换算成整数区间 问题二:数据结构 假定数据随机,出题者确实没有卡 ,那么区间的个数不会很多,好像链表模拟也可以...

Ansicht's Blog 418

6637. 【GDOI2020.5.16模拟】Three

题目 给你一个数列(保证两两数字不相同)。 对于一个区间,它的“价值”定义为前三大数的乘积。 求所有的长度大于等于333的区间的价值之和。 思考历程 论自闭是什么感受…… 当旁边人都切了,都在说这题是水题,但却想不出来的时候…… 在最后不到一个钟的时间里用我那愚钝至极的大脑想出了个常数极大的做法: 按照套路,右端点扫过去,维护每个左端点到这个右端点的信息(最大值、次大值、第三大值)。 想象暴力修改是什么样子。将最大值、次大值、第三大值的序列从上到下并列地写在一起。 当对一个位置进行修改的时候,找到这个值插

A1847225889的博客 353
上一篇: Codeforces 1251D Salary Changing (二分+最大化中位数)
synapse7
博客等级 码龄14年 432粉丝 701原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值