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

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

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

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

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

国外珂学家 滑稽)


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

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


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

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

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

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


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

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

先写个结构体。

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

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

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

  1. 建立你的珂朵莉树
  1. ls = 1;
  2. for ( Re int i = 1; i <= N; ++i ) scanf( "%d", &a[i] );
  3. 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;
  4. S.insert( node( ls, N, a[N] ) );

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

举个例子:

  1. 111001100011000

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

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

炒鸡简单对吧?

  1. Split

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

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

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

举例子:

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

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

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

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

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

为什么呢?

举反例——

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

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

  1. 区间取反

暴力枚举即可(也要Split)

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

也很暴力,一个个枚举

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

还是暴力

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

差不多就这些了。

骗分大法好啊!

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

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define Re register
  4. struct node{
  5. int l, r; mutable bool val;
  6. node( int L, int R = -1, int v = 0 ):l(L), r(R), val(v){}
  7. bool operator < ( const node t )const{ return l < t.l; }
  8. };
  9. #define IT set<node>::iterator
  10. set<node> S;
  11. inline IT Split( Re int pos ){
  12. Re IT t(S.lower_bound(node(pos)));
  13. if ( t != S.end() && t->l == pos ) return t;
  14. t--;
  15. Re int L(t->l), R(t->r); Re bool v(t->val);
  16. S.erase(t);
  17. S.insert( node( L, pos - 1, v ) );
  18. return S.insert( node( pos, R, v ) ).first;
  19. }
  20. inline void Assign( Re int l, Re int r, Re bool v ){
  21. Re IT ed(Split(r + 1)), be(Split(l));
  22. S.erase( be, ed );
  23. S.insert(node( l, r, v ));
  24. }
  25. inline void Change( Re int l, Re int r ){
  26. Re IT ed(Split(r + 1)), be(Split(l));
  27. for ( Re IT it = be; it != ed; ++it ) it->val = !(it->val);
  28. }
  29. inline int Get1( Re int l, Re int r ){
  30. Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0);
  31. for ( Re IT it = be; it != ed; ++it ) if ( it->val ) ans += (it->r) - (it->l) + 1;
  32. return ans;
  33. }
  34. inline int Get2( Re int l, Re int r ){
  35. Re IT ed(Split(r + 1)), be(Split(l)); Re int ans(0), cur(0);
  36. for ( Re IT it = be; it != ed; ++it )
  37. if ( it->val ) cur += (it->r) - (it->l) + 1;
  38. else ans = max( ans, cur ), cur = 0;
  39. ans = max( ans, cur );
  40. return ans;
  41. }
  42. int N, M, t, ls;
  43. int a[100005];
  44. int main(){
  45. scanf( "%d%d", &N, &M ); ls = 1;
  46. for ( Re int i = 1; i <= N; ++i ) scanf( "%d", &a[i] );
  47. 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;
  48. S.insert( node( ls, N, a[N] ) );
  49. for ( Re int i = 1; i <= M; ++i ){
  50. Re int op, a, b; scanf( "%d%d%d", &op, &a, &b ); a++; b++;
  51. if ( op < 2 ) Assign( a, b, op );
  52. if ( op == 2 ) Change( a, b );
  53. if ( op == 3 ) printf( "%d\n", Get1( a, b ) );
  54. if ( op == 4 ) printf( "%d\n", Get2( a, b ) );
  55. }
  56. return 0;
  57. }

「学习笔记」珂朵莉树 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. 2014年最热门的国人开发开源软件TOP100

    2014年最热门的国人开发开源软件TOP100 不知道从什么时候开始,很多一说起国产好像就非常愤慨,其实大可不必.做开源中国六年有余,这六年时间国内的开源蓬勃发展,从一开始的使用到贡献,到推出自己很多 ...

  2. 9 模版语言 jinja2

    from flask import Flask,redirect,render_template,jsonify,send_file,request,Markup,sessionimport json ...

  3. 2019-7-29-NetBIOS-计算机名称命名限制

    title author date CreateTime categories NetBIOS 计算机名称命名限制 lindexi 2019-07-29 09:59:17 +0800 2018-12- ...

  4. rsa加密(非对称加密)

    rsa加密 是非对称加密 需要公钥 与 私钥 这个公钥私钥的具体值需要与后端协商定下 rsa js代码如下 代码太多不插入了 html代码如下 <!DOCTYPE html> <ht ...

  5. Java如何计算hashcode值

    在设计一个类的时候,很可能需要重写类的hashCode()方法,此外,在集合HashSet的使用上,我们也需要重写hashCode方法来判断集合元素是否相等. 下面给出重写hashCode()方法的基 ...

  6. HDU 1879 还是prim最小生成树、

    #include<stdio.h> #include<math.h> #include<string.h> +,MAX=1e7; int vis[qq]; int ...

  7. uni-app学习记录06-Vuex简单使用

    import Vue from 'vue' // 这里引入vuex import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Stor ...

  8. tf.variable_scope 参数

    最近在看TensorFlow的变量管理,发现很多代码中tf.variable_scope()参数的数量及意义还不太清楚,特此记录: def __init__(self, name_or_scope, ...

  9. 解决input number类型上下滚动 禁用滚轮事件

    1.去掉input在type="number"时的上下箭头 <style> input::-webkit-outer-spin-button,input::-webki ...

  10. H3C 用三层交换机实现VLAN间路由