珂朵莉树

· · 个人记录

前言

ODT一定要数据随机的情况下使用,不然会被卡的很惨

简述

珂朵莉树由一位长发飘逸的帅哥---lxl发明

由于发明者原因,所以也叫ODT:Old Driver Tree

复杂度 O(m log2 n) ,由于数据随机,期望有一部分的操作为assign,set的大小趋于logn,因此这种数据结构复杂度接近mlog2n

但是常数较大,set有时会捅你一刀,但是现在比赛好像开O2(bushi

可以做区间推平和区间多少个k~~我才不会说我只会这个呢~~

正题

题目

给定你一个长度为n的序列,k次询问,每次询问在[ l , r ]有多少个c,并将整个区间改为c

n,m<=10 ^ 6

样例输入


10 6  

0  0  0  0  0  0  0  0  0  0

1  3  1

2  5  2

3  10 3

5  9  3

4  9  4

7  10 3

#### 样例输出

0

0

0

5

0

1

做法

很显然,如果直接每次去暴力修改直接给你T飞

用分块每次修改最后也是期望 n\sqrt n, 1e9也是会T掉

线段树码量大,难查询

所以有请今天的主角------ODT

思路

将一段连续的数字合并成一个点,每次进行区间推平时,先把包含两段区间的节点分裂开,将分裂得到的包含l的点和包含r的点合并

具体做法

详情见注释

开一个set,存储一段连续的区间

初始时把每个位置都存进去,因为自身就是一个连续的相同的数

每次在推平时先将包含r+1的节点和包含l的节点分裂(代码中会提到

将中间全部擦除,插入新节点{l,r,c}

如果需要查询,在擦除之前在 r+1的节点和l的节点中间扫一遍查询即可

Code(带注释

#include<iostream>
#include<set>
using namespace std;
struct node{
    int l,r;//范围 
    int v;//相同的值 
    bool operator < (const node &a) const{
        return l<a.l;//便于查询进行分裂 
    }
};
set<node>aa;
int n;
int a[1000000];
set<node>::iterator split(int x)
{
    set<node>::iterator find=aa.lower_bound(node{x,x,0});//找到包含这个节点的点 
    if(find!=aa.end()&&find->l==x)//如果找到了并且这个节点左端点就是要找的位置 即  不必要分裂 
    {
        return find;//直接返回即可 
    }
    find--;//找到的是第一个大于等于,由于等于的情况判完,一定是大于的 
    int l=find->l;// ->是指针用法 
    int r=find->r;
    int v=find->v;
    aa.erase(find); //将原节点擦除 
    aa.insert(node{l,x-1,v}); // 分裂出左区间 
    return aa.insert(node{x,r,v}).first; //返回最后一个区间 
}
int assign(int l,int r,int x)
{
    set<node>::iterator itr=split(r+1),itl=split(l);//先对r+1和l进行分裂
    /*感性理解 
        这里为什么要对r+1和l进行操作呢,我个人也思考了很久 
        因为你返回的是那个节点插入的右边,也就是说另一半才包含你查找的东西
        你对r+1进行分裂,返回的是r+1到那个节点右端点,所以擦除的时候会擦出掉r
        那么你 分裂l是一定包含l的  
    */ 
    int res=0;
    for(set<node>::iterator i=itl;i!=itr;i++)
    {
        if(i->v==x) res+=i->r-i->l+1; //对于 l-r中间查询 
    }
    aa.erase(itl,itr);//推平 
    aa.insert(node{l,r,x});//插入新区间 
    return res;//回答查询 
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];//输入 
    }
    int j=1;
    for(int i=1;i<=n;i++)
    {
        aa.insert(node{i,i,a[i]}); //每个位置都初始化成一个点,便于以后合并 
    }
    for(int i=1;i<=n;i++)
    {
        int l,r,c;
        cin>>l>>r>>c;
        cout<<assign(l,r,c)<<endl;//调用函数 
    }
}