首先 让我们进入珂学的大门 OvO:

珂朵莉树的起源 模板题 QvQ

(ノ≧∇≦)ノ  珂朵莉 

s

dsf
sd

好了好了,先别欣赏了,看题目首先题意

s

1、什么是珂朵莉树

珂朵莉树,Chtholly Tree 又称 Old Driver Tree(ODT)(老司机树)。

是一种基于 set 的暴力数据结构。

2、什么时候用珂朵莉树

使一整段区间内的东西变得一样,数据随机。
(这是由于其复杂度和 assign 的操作次数有关,所以当数据随机时会有 $1/4$的概率进行 assign 操作正因为如此复杂度海星)$O(m~log(n)) n,m<=10^5$

3. 初始化

    struct node
    {
        int l,r;
        mutable ll v;//一定要记得 加 mutalbe QAQ
        node(int ls,int rs=-1,ll vv=0) : l(ls),r(rs),v(vv) {}
        bool operator < (const node& ano) const{
            return l < ano.l;
        }
    };//相当于包括了一个三元组表示 [l,r] 区间里有所有数都是 v;
//我们用 set 存储这些三元组,内部维护这些区间,以左端点 L 作为关键字进行升序排列,
//这样方便我们进行一些操作的时候方便查找到我们想要修改的区间。
    set<node> s;
    #define itset set<node> :: iterator 

Chtholly Tree 的本质就是把区间缩成点,set 维护的 Chtholly Tree ,可以视为一棵缩点后的平衡树。需要注意的是 mutable,意为易变的,不定的。它对 v 的修饰,使得我们可以在 add 操作中修改 v 的值。没有它的修饰会在 add 函数里导致 CE

4. 核心操作

操作 split(pos)指将原来含有 pos 位置的节点分成两部分:$[l,pos-1]$和 $[pos,r]$。
由于在 $set$中的所有节点是有序的,我们可以用 $lower\_bound(x)$定位我们要操作的区间。(注: $lower\_bound(x)$可以在 $logn$时间内在一个有序序列中找到最小的 $y$ 使得 $y>=x$

这时我们要分以下两种情况处理通过 $p$ 找到的区间:

1. 如果 $p$ 已经是某个区间的左端点,那么我们直接返回查找到的迭代器;

2. 如果 $p$ 不是某个区间的左端点,那么当前返回的迭代器所指向的区间是 $p$ 所在区间右边第一个区间。这时将迭代器前移一位。如果在前移之后, $p$ 比当前区间的右端点还大,那么直接返回尾迭代器 (因为不需要进行操作)

3. 否则记录一下当前区间的信息,然后直接删除当前区间再插入拆分好的区间 $[L,p-1]$ 和 $[p,R]$ ,最后返回 $p$ 所在区间的迭代器 (操作只会影响包含 $p$ 的区间)。

    itset split(int pos){
        itset it = s.lower_bound(node(pos));

        if(it != s.end() && it->l == pos){
            return it;
        }

        --it;
        if(it->r < pos) return s.end();
        //我不是清楚这个特判到底要不要,因为似乎不加都可以 AC QAQ 求解释
        int ls=it->l , rs=it->r;//[l,r] 就是要分裂的区间
        ll vv=it->v;
        s.erase(it);//删除原节点
        s.insert(node(ls,pos-1,vv));
        return s.insert(node(pos,rs,vv)).first;
        //插入后半段,返回后半段的迭代器。
        //这里利用了 pair<iterator,bool> insert (const value_type& val) 的返回值。
    }

那么接下来 就看看具体的用法

sd

1. 区间赋值

有了 $split$之后,我们就能利用 $split$做各种神 ($ba$o) 奇 ($li$) 的操作了

给一个区间所有的数赋同一个值

首先我们先把 $r+1$ 和 $L$ 所在区间各自拆分出来。既然我们是区间赋值,我们可以直接将 $[L,R)$ 从 set 中移除再添加一个新的 $[L’,R’]$ 区间,其中的值为 $va$l, 而且由于其中有很多小区间被合并在 $[L’,R’]$, 空间复杂度会大大减小

    void assign_v(int l,int r,ll v){
        itset itrs=split(r+1) , itls = split(l);//求出要被摊平区间的收尾地址
        s.erase(itls,itrs);
        s.insert(node(l,r,v));
    }

把 $l$和 $r+1$进行 $split$,再把 $[l,r]$合成一个节点。首先为什么要先 $split$出 $r+$1 呢?因为如果先 $split$出 $l$,再 $split$出 $r+1$,之前的 $itls$可能就不是 $l$对应的迭代器了。QAQ

void erase (iterator first, iterator last)表示可删除 $[first,last)$区间。这里就是把 $[l,r+1)$内的部分推成一段。

2. 区间加法

由于这里 set 的 node 定义我们直接遍历每一集合修改就行了(神 (bao) 奇 (li) 吧 OvO)

    void add(int l,int r,ll val){
        itset itrs = split(r+1) , itls = split(l);
        for(; itls != itrs ; ++itls)
            itls->v += val;
    }

3. 区间 $k$次幂和

和加法很类似 , 不过要记得乘以区间的大小

    ll sum(int l,int r,int k,int p){
        itset itrs = split(r+1) , itls = split(l);
        ll res = 0 ;
        for(;itls != itrs ; ++itls)//遍历的时候千万不要写错 itls 和 itrs wwwww
            res = (res + (ll) (itls->r - itls->l +1) * (quickpow(itls->v , (ll)k ,(ll)p))) % p;
        return res;
    }

4. 区间第 $k$小

很简单的操作 (但是这玩意可做不能区间第 k 小的模板题啊

第一步依旧是拆分 $r+1$ 与 $L$ 。然后我们将 $[L’, R’)$ 中所有区间拿出来,将区间长度和区间值存到 $pair$,放到 $vector$里,然后对其中所有区间按照 $ v$ 升序排列。然后遍历 $vector$,每次将 $k$ 减去当前区间长度,直到 $ k<=0$ 。十分的神 (bao) 奇 (li),但是由于数据随机和区间合并时导致区间数量减少,我们依旧能过题

    ll ranks(int l,int r,int k){
        vector<pair<ll ,int > > v;
        //first 放值 , second 放数量
        itset itrs = split(r+1) , itls = split(l);

        v.clear();

        for(;itls != itrs ; ++itls)
            v.push_back(pair<ll,int> (itls->v , itls->r - itls->l+1));

        sort(v.begin() , v.end());

        for(vector<pair<ll,int> > :: iterator its=v.begin(); its!=v.end(); ++its){

            k -= its->second;
            if(k <= 0)
                return its->first;
        }
        return -1ll;//如果没有,则返回 -1;
    }

最后就上完整代码(注意此题一定要注意精度,CF 题诡异

#include <cstdio>
#include <iostream>
#include <map>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <ctime>
#include <vector>
#include <queue>
#include <set>
using namespace std;

#define fors(i,a,b) for(int i=(a);i<=(b);++i)
#define mem(x,i) memset((x),(i),sizeof((x)))
#define max(x,y) ((x) < (y) ? (y) : (x))
#define min(x,y) ((x) < (y) ? (x) : (y))
#define abs(x) ((x) < 0 ? -(x) : (x))
#define forvit(i) for(vector<es> :: iterator it=(i).begin();it!=(i).end();++it) 
#define Debug(...) fprintf(stderr,__VA_ARGS__)
#define Tostring(x) #x;
#define Link(x,y) x##y
#define IO(x) freopen(""#x".in","r",stdin);freopen(""#x".out","w",stdout);

typedef long  long ll; 
ll read(){
    ll s=0,f=1;
    char c=getchar();
    while(c > '9' || c < '0') {if(c == '-') f=-1;c=getchar();}
    while(c >= '0' && c <= '9') {s=s*10+c-48; c=getchar();}
    return s*f;
}

void write(ll x){
    if(!x) putchar('0');
    if(x < 0) x = -x , putchar('-');

    static int sta[36];
    int tot=0;
    while(x){
        sta[tot++] = x % 10;
        x /= 10;
    }
    while(tot)
        putchar(sta[--tot] + 48);
}

const int maxn = 1e5+7 ;
int n,m;
ll a[maxn];
ll pows(ll a,ll b,ll p){
    ll res=1;
    a %= p;
    while(b){
        if(b & 1) res = (res * a) % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}
namespace Chtholly_Tree{
    struct node
    {
        int l,r;
        mutable ll v;
        node(int ls,int rs=-1,ll vv=0) : l(ls),r(rs),v(vv) {}
        bool operator < (const node& ano) const{
            return l < ano.l;
        }
    };
    set<node> s;
    #define itset set<node> :: iterator 

    itset split(int pos){
        itset it = s.lower_bound(node(pos));
        if(it != s.end() && it->l == pos){
            return it;
        }
        --it;
        if(it->r < pos) return s.end();
        int ls=it->l , rs=it->r;
        ll vv=it->v;
        s.erase(it);
        s.insert(node(ls,pos-1,vv));
        return s.insert(node(pos,rs,vv)).first;
    }

    void assign_v(int l,int r,ll v){
        itset itrs=split(r+1) , itls = split(l);
        s.erase(itls,itrs);
        s.insert(node(l,r,v));
    }

    void add(int l,int r,ll val){
        itset itrs = split(r+1) , itls = split(l);
        for(; itls != itrs ; ++itls)
            itls->v += val;
    }
    ll ranks(int l,int r,int k){
        vector<pair<ll ,int > > v;
        itset itrs = split(r+1) , itls = split(l);
        v.clear();
        for(;itls != itrs ; ++itls)
            v.push_back(pair<ll,int> (itls->v , itls->r - itls->l+1));
        sort(v.begin() , v.end());
        for(vector<pair<ll,int> > :: iterator its=v.begin(); its!=v.end(); ++its){
            k -= its->second;
            if(k <= 0)
                return its->first;
        }
    }

    ll sum(int l,int r,int ex,int p){
        itset itrs = split(r+1) , itls = split(l);
        ll res = 0 ;
        for(;itls != itrs ; ++itls)
            res = (res + (ll) (itls->r - itls->l +1) * (pows(itls->v , (ll)ex ,(ll)p))) % p;
        return res;
    }
};
using namespace Chtholly Tree;
const int MOD7 = 1e9 + 7;
ll seed, vmax;//此题诡异的读入方式 以下可以忽略
ll rd()
{
    ll ret = seed;
    seed = (seed * 7 + 13) % MOD7;
    return ret;
}

int main(int argc, char const *argv[])
{
    cin>>n>>m>>seed>>vmax;
    fors(i,1,n){
        a[i] = (rd() % vmax) + 1;
        s.insert(node(i,i,a[i]));
    }
    s.insert(node(n+1,n+1,0));//注意 [l,r) 问题 所以最后在加一个 n+1
    for (int i =1; i <= m; ++i)
    {
        int op = int(rd() % 4) + 1;
        int l = int(rd() % n) + 1;
        int r = int(rd() % n) + 1;
        if (l > r)
            swap(l,r);
        int x, y;
        if (op == 3)
            x = int(rd() % (r-l+1)) + 1;
        else
            x = int(rd() % vmax) +1;
        if (op == 4)
            y = int(rd() % vmax) + 1;
        if (op == 1)
            add(l, r, ll(x));
        else if (op == 2)
            assign_v(l, r, ll(x));
        else if (op == 3)
            cout<<ranks(l,r,x)<<endl;
        else
            cout<<sum(l,r,x,y)<<endl;
    }
    return 0;
}

例题:开始尽情的水题了(Chtholly Tree 30min 解决 QvQ

1. 序列操作 取反要 ^=1

2. 酒店 Hotel (不怎么适合,Chtholly 会 T 一个点 QAQ,所以直接打暴力吧)

3.Physical Education Lessons(在 assign 里直接一下 sum 计数)

4. 区间覆盖 (加强版)

5. 语文 1(chin1)- 理理思维 小范围桶排序,v 设为字符,不要乱强制转化 int)

6. 脑洞治疗仪 (脑洞暴力搞一搞)

最后祝大家 (ノ≧∇≦)ノ NOIP 2018 while(1) ++rp;

分类: 文章

B_Z_B_Y

Let us go go go!!!!!!  ☆ * .  ☆   . ∧_∧ ∩ * ☆ * ☆ ( ・∀・)/ .  . ⊂   ノ* ☆ ☆ * (つ ノ .☆    (ノ

4 条评论

B_Z_B_Y · 2018年10月22日 8:29 上午

注意一下 namespace Chtholly Tree 应该是 namespace Chtholly_Tree 少了一个下划线 QvQ

    XZYQvQ · 2018年10月22日 12:18 下午

    已经帮您修正了 QvQ

litble · 2018年10月19日 7:46 上午

BGM 名:Always in my heart

别谢我,我叫珂学家

    XZYQvQ · 2018年10月19日 9:23 上午

    这个暴力吼哇
    QwQ 赶紧去玩一波

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注