珂朵莉树,也叫ODT(Old Driver Tree 老司机树)

从前有一天,珂朵莉出现了。。。

然后有一天,珂朵莉树出现了。。。

看看图片的地址 Codeforces可还行)

没错,珂朵莉树来自Codeforces 896C C. Willem, Chtholly and Seniorious

国外珂学家 滑稽)


前置芝士:
set的基本操作
迭代器(跟指针差不多
重载运算符、构造函数的简单了解
mutable(下面也会讲
暴力枚举
常数优化(inline O2 O3 register大法好啊

够简单了吧?除了真正的小白,大家都应该有所了解。


废话完了,扯进正题(毕竟你不是珂学家,你是个O·I·E·R)。

珂朵莉树的适用范围(缺一不可,不然复杂度就是不正确的,很容易被卡):

  1. 数据纯随机
  2. 有区间修改操作

大概就这两个吧。珂朵莉树毕竟是一种骗分算法(珂朵莉:我不服),想到正解尽量用正解。


珂朵莉树的主要思想就是用一个set来维护元素相同的区间。

这里我们以P2572 [SCOI2010]序列操作为例,讲一讲珂朵莉树。

先写个结构体。

#define Re register //卡常操作
struct node{
int l, r; mutable bool val;
node( int L, int R = -1, int v = 0 ):l(L), r(R), val(v){}//构造函数
bool operator < ( const node t )const{ return l < t.l; }//重载运算符
};

l表示左边界,r表示右边界,val表示l~r保存的值都是val(当然,根据题目需要,val的类型可以改变)。

mutable的作用很简单。由于在set中,元素是以常量方式存储的,不能直接修改。在set中我们是按l排序的,修改val的值实际上没有关系,不会影响set中元素的顺序,把val的类型前加个mutable,就可以直接修改val,否则还要删除元素,再插入进去,降低了效率。因为珂朵莉树比较暴力,我们要尽可能优化复杂度。

  1. 建立你的珂朵莉树
	ls = 1;
for ( Re int i = 1; i <= N; ++i ) scanf( "%d", &a[i] );
for ( Re int i = 2; i <= N; ++i ) if ( a[i] ^ a[i - 1] ) S.insert( node( ls, i - 1, a[i - 1] ) ), ls = i;
S.insert( node( ls, N, a[N] ) );

直接把连续的一段段插进去即可。

举个例子:

111001100011000

我们就会插入以下几个元素(以 l、r、val顺序

1 3 1
4 5 0
6 7 1
8 10 0
10 11 1
12 15 0

炒鸡简单对吧?

  1. Split

学过FHQ Treap的童鞋听到这个很熟悉对吧?其实它们作用是差不多的,但是由于FHQ Treap是以二叉查找树结构存储的,但这里的珂朵莉树直接用set存,相对来说简单得多。

Split(pos)的作用就是在某个包含pos的区间[l,r]中,分成两个区间[l,pos - 1],[pos,r]。实现很简单,请看代码。

inline IT Split( Re int pos ){
Re IT t(S.lower_bound(node(pos)));//找到左边界第一个大于等于它的元素
if ( t != S.end() && t->l == pos ) return t; // 如果左边界就是这个元素,不用分了,直接返回[pos,r]也就是[l,r]
t--;//前一个元素就是包含pos的区间
Re int L(t->l), R(t->r); Re bool v(t->val);//存下来把原来的信息
S.erase(t);//删了它!
S.insert( node( L, pos - 1, v ) );//插入区间[l,pos - 1]
return S.insert( node( pos, R, v ) ).first;//插入区间[pos,r]并返回[pos,r]的迭代器
}

举例子:

如果把上面那个例子中,Split(2)
t 指向[4,5](4是第一个大于等于2的)
左边界不是2,t--,指向区间[1,3]
分成两个区间[1,1][2,3]
返回[2,3]的迭代器
  1. Assign

这个操作用于区间修改元素。由于这个操作可以迅速减少set中元素的个数,所以这是珂朵莉树的复杂度保证。

也十分简单,就是把边界Split,中间全部删除再插入一个元素就好了。

inline void Assign( Re int l, Re int r, Re bool v ){//把l到r所有元素统统变成v
Re IT ed(Split(r + 1)), be(Split(l));//Split边界 分成[...l-1] {[l...]...[..r]} [r+1...] be指向;[l...],ed指向[r+1...] 大括号中间全部要删除
S.erase( be, ed );//删去be~ed-1的所有元素,就是大括号中间的部分
S.insert(node( l, r, v ));//插入区间[l,r]
}

有一个小细节,要先执行Split(r+1),再执行Split(l)

为什么呢?

举反例——

还是拿建树那里的例子
Assign(2,2)
假设先执行Split(2)
第一个区间[1,3]变成了[1][2,3]
be指向区间[2,3]
再执行Split(3)时
[2,3]变成了[2][3]
ed指向[3]
然后如果调用了be
be原指向的区间[2,3]已经被删除了
然后RE*8+TLE*1+AC*1

没错反过来的目的就是避免Split右区间时把be指向的区间删了。

  1. 区间取反

暴力枚举即可(也要Split)

inline void Change( Re int l, Re int r ){
Re IT ed(Split(r + 1)), be(Split(l));
for ( Re IT it = be; it != ed; ++it ) it->val = !(it->val);
}
  1. 查询1的个数

也很暴力,一个个枚举

inline int Get1( Re int l, Re int r ){
Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0);
for ( Re IT it = be; it != ed; ++it ) if ( it->val ) ans += (it->r) - (it->l) + 1;
return ans;
}
  1. 查询最长连续1的个数

还是暴力

inline int Get2( Re int l, Re int r ){
Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0), cur(0);
for ( Re IT it = be; it != ed; ++it )
if ( it->val ) cur += (it->r) - (it->l) + 1;
else ans = max( ans, cur ), cur = 0;
ans = max( ans, cur );
return ans;
}

差不多就这些了。

骗分大法好啊!

完整代码(https://www.luogu.org/problemnew/show/P2572)

#include<bits/stdc++.h>
using namespace std;
#define Re register struct node{
int l, r; mutable bool val;
node( int L, int R = -1, int v = 0 ):l(L), r(R), val(v){}
bool operator < ( const node t )const{ return l < t.l; }
}; #define IT set<node>::iterator
set<node> S; inline IT Split( Re int pos ){
Re IT t(S.lower_bound(node(pos)));
if ( t != S.end() && t->l == pos ) return t;
t--;
Re int L(t->l), R(t->r); Re bool v(t->val);
S.erase(t);
S.insert( node( L, pos - 1, v ) );
return S.insert( node( pos, R, v ) ).first;
} inline void Assign( Re int l, Re int r, Re bool v ){
Re IT ed(Split(r + 1)), be(Split(l));
S.erase( be, ed );
S.insert(node( l, r, v ));
} inline void Change( Re int l, Re int r ){
Re IT ed(Split(r + 1)), be(Split(l));
for ( Re IT it = be; it != ed; ++it ) it->val = !(it->val);
} inline int Get1( Re int l, Re int r ){
Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0);
for ( Re IT it = be; it != ed; ++it ) if ( it->val ) ans += (it->r) - (it->l) + 1;
return ans;
} inline int Get2( Re int l, Re int r ){
Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0), cur(0);
for ( Re IT it = be; it != ed; ++it )
if ( it->val ) cur += (it->r) - (it->l) + 1;
else ans = max( ans, cur ), cur = 0;
ans = max( ans, cur );
return ans;
} int N, M, t, ls;
int a[100005]; int main(){
scanf( "%d%d", &N, &M ); ls = 1;
for ( Re int i = 1; i <= N; ++i ) scanf( "%d", &a[i] );
for ( Re int i = 2; i <= N; ++i ) if ( a[i] ^ a[i - 1] ) S.insert( node( ls, i - 1, a[i - 1] ) ), ls = i;
S.insert( node( ls, N, a[N] ) );
for ( Re int i = 1; i <= M; ++i ){
Re int op, a, b; scanf( "%d%d%d", &op, &a, &b ); a++; b++;
if ( op < 2 ) Assign( a, b, op );
if ( op == 2 ) Change( a, b );
if ( op == 3 ) printf( "%d\n", Get1( a, b ) );
if ( op == 4 ) printf( "%d\n", Get2( a, b ) );
}
return 0;
}

「学习笔记」珂朵莉树 ODT的更多相关文章

  1. 珂朵莉树(ODT)笔记

    珂朵莉树,又叫老司机树($Old\, Driver \, Tree$) 是一种暴力出奇迹,就怕数据不随机的数据结构. 适用 需要用线段树维护一些区间修改的信息…… 像是区间赋值(主要),区间加…… 原 ...

  2. [转]我的数据结构不可能这么可爱!——珂朵莉树(ODT)详解

    参考资料: Chtholly Tree (珂朵莉树) (应某毒瘤要求,删除链接,需要者自行去Bilibili搜索) 毒瘤数据结构之珂朵莉树 在全是珂学家的珂谷,你却不知道珂朵莉树?来跟诗乃一起学习珂朵 ...

  3. Chtholly Tree (珂朵莉树) ODT

    ODT,OldDriverTree,又名ChthollyTree" role="presentation" style="position: relative; ...

  4. 珂朵莉树(Chtholly Tree)学习笔记

    珂朵莉树(Chtholly Tree)学习笔记 珂朵莉树原理 其原理在于运用一颗树(set,treap,splay......)其中要求所有元素有序,并且支持基本的操作(删除,添加,查找......) ...

  5. LOJ#557. 「Antileaf's Round」你这衣服租来的吗(FHQ Treap+珂朵莉树)

    题面 传送门 题解 好吧我是不太会复杂度分析-- 我们对于每种颜色用一个数据结构维护(比方说线段树或者平衡树,代码里写的平衡树),那么区间询问很容易就可以解决了 所以现在的问题是区间修改,如果区间颜色 ...

  6. 洛谷$P2572\ [SCOI2010]$ 序列操作 线段树/珂朵莉树

    正解:线段树/珂朵莉树 解题报告: 传送门$w$ 本来是想写线段树的,,,然后神仙$tt$跟我港可以用珂朵莉所以决定顺便学下珂朵莉趴$QwQ$ 还是先写线段树做法$QwQ$? 操作一二三四都很$eas ...

  7. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

  8. 「学习笔记」字符串基础:Hash,KMP与Trie

    「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...

  9. 洛谷AT2342 Train Service Planning(思维,动态规划,珂朵莉树)

    洛谷题目传送门 神仙思维题还是要写点东西才好. 建立数学模型 这种很抽象的东西没有式子描述一下显然是下不了手的. 因为任何位置都以\(k\)为周期,所以我们只用关心一个周期,也就是以下数都在膜\(k\ ...

随机推荐

  1. 06Redis入门指南笔记(安全、通信协议、管理工具)

    一:安全 1:可信的环境 Redis以简洁为美.在安全层面Redis也没有做太多的工作.Redis的安全设计是在"Redis运行在可信环境"这个前提下做出的.在生产环境运行时不能允 ...

  2. 12 Top Open Source Data Analytics Apps

    1. Hadoop It would be impossible to talk about open source data analytics without mentioning Hadoop. ...

  3. 最适合 Python 入门的资源有哪些?

    https://blog.csdn.net/zV3e189oS5c0tSknrBCL/article/details/81230593 学习任何一门编程语言或者技能基本上都遵循3个步骤,第一步是看,第 ...

  4. Websocket 简单对话:静态网页与pycharm对话

    WebSocket websocket 是一种在单个Tcp连接上进行双全工通信的协议.websocket通信协议于2011年被IETF定为标准RFC6455,并 由RFc7936补充规范.WebSoc ...

  5. Codeforces Round #577 (Div 2)

    A. Important Exam 水题 #include<iostream> #include<string.h> #include<algorithm> #in ...

  6. mac常用快捷键,Mac文件重命名快捷键,Mac OS快速访问系统根目录, MacOS 10.11重要数据的存储位置大全

    command+r,相当于F5,刷新页面 command+F5,启动voiceover command+q 关闭当前程序 在Finder中command+/ 打开底部状态栏,可以查看剩余磁盘空间大小 ...

  7. setTimeout与setInterval有何区别?

    ①setTimeout和setInterval的语法相同.它们都有两个参数,一个是将要执行的代码字符串,还有一个是以毫秒为单位的时间间隔,当过了那个时间段之后就将执行那段代码. ②不过这两个函数还是有 ...

  8. SSM整合 mybatis多条件查询与分页

    多条件查询与分页: 通过页面的houseName.floorage获取值传到前端视图(HouseSearchVO)实体类中的houseName,floorage建立houseSearchVO对象. 通 ...

  9. springboot 项目打包可运行jar文件

    eclipse 运行run as  maven bulid  ,填入package ,运行打包 java -jar xxx.jar

  10. 京东基于Spark的风控系统架构实践和技术细节

    京东基于Spark的风控系统架构实践和技术细节 时间 2016-06-02 09:36:32  炼数成金 原文  http://www.dataguru.cn/article-9419-1.html ...