珂朵莉树(永远喜欢珂朵莉/doge)

目录

前言

可能用到前置知识

背景

构建珂朵莉树

核心函数

珂朵莉树在实际题目使用

对珂朵莉树的一些感想

最后的最后 



前言

最近刚刚学内容大概是借鉴的吧,感觉这个数据结构不仅简单,还很强,有着非常柯学科学的时间复杂度,处理问题充满了暴力的美感,主要可以解决一些区间问题,在随机数据因为暴力很容易被卡下时间复杂度优秀,比线段树此类数据结构有着更小的常数。

可能用到前置知识

 1.c++set(现在学也只是几分钟吧/doge)

2.会暴力

背景

来源是一道远古div1C的题,也是珂朵莉树的模板题我还没写过,最后会给出这道模板题。

关于她柯学的另一个别名——ODT,来源是其发布者Old driver。

虽然这玩意名字怪怪的,但他的操作还是很简单的,很少的不都是暴力吗

大家别被它的名字吓到,其实他的本质就是一个set<pair<int,int>>大概吧,再根据题目去维护一些信息,然后用这个set去存下每一个信息不同的区间,处理过程中保证每个区间内部信息相同


构建珂朵莉树

set<Node> st;

好了

当然还要再处理一下,对这个珂朵莉树进行初始化。

比如建立用珂朵莉树维护一下[1,n],初始化值为1的区间,两边建立两个哨兵。

这里有两个点,首先要对这个结构体的小于号重载我不会告诉你我在学完珂朵莉树以后才会重载符号,我们以一个区间的左端点为key进行排序,第二个要注意要维护的值要设置成mutable,不然会无法修改不知道原因(狗头保命)。

struct Node{
    int l,r;
    mutable int v;
    bool operator<(const Node &a)const
    {
        return l < a.l;
    }
};

int main()
{
    st.insert({0,0});
    st.insert({1,n,1});
    st.insert({n+1,n+1});
}

如果初始时每一个点内的数值不同,可以这样初始化 

st.insert({0,0});
for(int i = 1 ; i <= n ; i++)
{
    int x;
    cin>>x;
    st.insert({i,i,x});
}
st.insert({n+1,n+1});

核心函数

那么接下来就是珂朵莉树的最玄学强大之处 。

再次之前,由于我们要用到set的迭代器,由于这玩意实在是又臭又长,参考了大佬的做法以后,一般可以这样宏定义(你想更简单一点也ok)

#define S_IT set<Node>::iterator

split函数

这个函数帮助我们找到并划分区间,这里再提一嘴,迭代器不能进行类似与it+=1这样的操作大概被读者猜到博主是个语法都没学好的蒟蒻

S_IT split(int pos)
{
    auto it = st.lower_bound({pos});
    if(it->l==pos)return it;
    it--;
    int l = it->l,r = it->r;
    auto s = it->s;
    st.erase(it);
    st.insert({l,pos-1,s});
    return st.insert({pos,r,s}).first;//.first可以返回插入位置的迭代器
}

我们找到以pos为左端点的第一个区间,如果存在这样这样的区间就返回它的迭代器。

不存在就把包含pos的区间裂开,创造出pos为左端点的区间

注意一下这里要先找右端点,不然split函数可能会把it1删除。 

 assign函数

这个函数对于珂朵莉树更为关键(split函数未必要使用),它的使用直接影响了珂朵莉树的时间复杂度,使其不至于退化为完全的暴力。

在这个函数里,我们可以对区间进行操作,下面举两个例子

  • 区间统计某一个数的个数
  • 区间求和
void assign(int l,int r,int v)//这个操作是把一个区间[l,r]变成同一个数v
{
    auto it2 = split(r+1),it1 = split(l);//通过两个split可以找到[l,r]区间
    auto temp = it1;//要保存一下it1,因为在下面的for迭代过程中it1将不再是原来的it1
    for(;it1!=it2;it1++)
    {
        //在这个for循环里可以进行各种区间操作
    }
    st.erase(temp,it2);//因为区间内都是同一个数,所以可以把这些区间合并为一个区间
    st.insert({l,r,v});//大大减少了区间数量,这也是保证不是暴力的关键
}
  • 区间求和:
    int sum = 0;
    for(;it1!=it2;it1++)
    {
        int len = it1->r - it1->l + 1;
        sum += len*it->v;
    }
  • 统计区间内为target的个数:
    int ans = 0;
    for(;it1!=it2;it1++)
    {
        if(it1->v==target)ans = it->r - it->l + 1;
    }

大家也发现了,珂朵莉树的暴力之处,大致处理问题的思路就是暴力找到区间,然后很直接得在这个区间内部操作就可以了。

关于它柯学的时间复杂度

 在随机数据的情况下,时间复杂度一般当作O(nlogn)

使用场景

一般在存在操作使得一段区间变成同一个数时使用,也就是常说的区间推平往往是珂朵莉树的标志 。

珂朵莉树在实际题目使用

 第一题是珂朵莉树的模板题,emmm...,虽然是模板题,但难度还是不小的。

有兴趣的可以自己取看看,这里贴一下我的第二题答案(最后一个数据会T)。

CF896C Willem, Chtholly and Seniorious - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/CF896C

P2787 语文1(chin1)- 理理思维 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P2787


#include<bits/stdc++.h>
#define rep(i,n) for(int i = 1 ; i <= n ; i++)
#define pb push_back
#define S_IT set<Node>::iterator
#define exp 1e-4
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
const int N = 5e4+10,M = N * 3,MAXN = 1,INF = 1e9,P = 998244353;
struct Node{
    int l,r;
    char c;
    bool operator<(const Node &a)const
    {
        return  l < a.l;
    }
};

set<Node> st;
char s[N];
int n,m,cnt[130];

S_IT split(int pos)
{
    auto it = st.lower_bound({pos});
    if(it->l==pos)return it;
    it--;
    int l = it->l,r = it->r;
    char c = it->c;
    st.erase(it);
    st.insert({l,pos-1,c});
    return st.insert({pos,r,c}).first;
}

int countChar(int l,int r,char c)
{
    auto it2 = split(r+1),it1 = split(l);
    int sum = 0;
    for(;it1 != it2 ; it1++)
    {
        if(it1->c==c)sum += it1->r - it1->l + 1;
    }
    return sum;
}

void assign(int l,int r,char c)
{
    auto it2 = split(r+1),it1 = split(l);
    st.erase(it1,it2);
    st.insert({l,r,c});
}

void sortChar(int l,int r)
{
    auto it2 = split(r+1),it1 = split(l);
    auto temp = it1;
    for(int i = 'A' ; i <= 'Z' ; i++)cnt[i] = 0;
    for(;it1 != it2 ; it1++)
    {
        cnt[it1->c] += it1->r - it1->l + 1;
    }
    st.erase(temp,it2);
    for(int i = 'A' ; i <= 'Z' ; i++)
    {
        int len = cnt[i];
        if(len)
        {
            st.insert({l,l+len-1,(char)i});
            l += len;
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    cin>>s;
    st.insert({0,0});
    st.insert({n+1,n+1});
    for(int i = 1 ; i <= n ; i++)
    {
        if(s[i-1]>='a')s[i-1] -= 'a' - 'A';
        st.insert({i,i,s[i-1]});
    }
    while(m--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int l,r;
            char c;
            cin>>l>>r>>c;
            if(c>='a')c -= 'a' - 'A';
            cout<<countChar(l,r,c)<<'\n';
        }
        else if(op==2)
        {
            int l,r;
            char c;
            cin>>l>>r>>c;
            if(c>='a')c -= 'a' - 'A';
            assign(l,r,c);
        }
        else
        {
            int l,r;
            cin>>l>>r;
            sortChar(l,r);
        }
    }
    return 0;
}

对珂朵莉树的一些感想

 一道题如果可以使用珂朵莉数,显然区间的个数不能太多,一般有两种情况

  • 存在较多的assign操作,总体上区间个数不会太多
  • 题目本身不会创造出太多的区间

可以想象,珂朵莉树的核心函数也不是必要的,关键是控制区间数量 。

最后的最后 

下面再给出两题,这两题都是最近的codeforces上的题。

第一题是我的珂朵莉树的入坑之题(但是这题并没有使用split,assign等函数)

其中第二题不能通过(很显然会被卡),但是你可以尝试过一下前面几个数据,体会一下珂朵莉树的暴力之美。 

Problem - E - Codeforceshttps://codeforces.com/contest/1638/problem/EProblem - E - Codeforceshttps://codeforces.com/contest/1642/problem/E最后希望大家都能成为最玄学科学的柯学家捏~~

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值