珂朵莉树

关于ODT个人的一些理解

\(\text{珂朵莉树使用条件}\):

1.数据随机 2.有区间赋值操作

\(\text{核心思想}\)

用一个\(\text{set< node >}\)来存取每一个节点

节点的定义:

一段连续相等的区间可以放在一个节点里面,分别用\(l\)\(r\)表示左右边界,\(v\)表示它们的值,节点默认按照左端点排序,各个节点精确覆盖\(1\)~\(n\)

也就是说,最开始有不同的\(n\)个小区间,每次进行区间赋值,就可以将目标区间中的所有小区间合并为一个大区间,来减少复杂度

\(\text{各个函数}\)

下面以 CF896C \(Willem\), \(Chtholly\) \(and\) \(Seniorious\)作为例子

关键操作

1.“建树”,将每个点\(node\){ \(i\) , \(i\) , \(vi\) }加入\(set\)

2.区间分裂:将一个区间按照指定的mid分割为
[ \(l\),\(mid\) ) , [ \(mid\) , \(r\) ],用\(lower\)_\(bound\)找到第一个左端点不小于\(mid\)的区间(那么目标区间要么是这一个,要么是前一个),删除目标区间,加入两个小区间

3.区间赋值:把该区间分离出来,用\(set\)自带函数\(erase\)删除整个区间(删除操作左闭右开),加入大区间

其他操作

4.区间加:分离出每个小区间,从左向右一个一个加

5.区间排名:分离出每个小区间,排序

6.区间幂次和:分离出每个小区间,直接求和,每个区间幂次和就是\((r-l+1)*v^k\)

\(Code\):

#include<bits/stdc++.h>
#define N 100005
#define MOD 1000000007 
#define IT set<Node>::iterator
using namespace std;
typedef long long ll;
int n,m;
ll seed,vmax;

ll quickpow(ll a,ll b,ll mod=LLONG_MAX)//快速幂 
{
    ll ret=1%mod;
    a%=mod;
    while(b)
    {
        if(b&1) ret=ret*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ret;
}
namespace ODT
{
    struct Node//ODT节点定义 
    {
        int l,r;
        mutable ll v;//要加mutable 
        Node(int l,int r=-1,ll v=0) : l(l),r(r),v(v) {}//构造函数 
        bool operator < (const Node &a)const//重载,set排序使用,默认按左端点排序 
        {
            return l<a.l;
        }
    };
    set<Node> s;
    
    IT split(int mid)//拆分 ,返回拆分后右边区间的指针 
    {
        IT it=s.lower_bound(Node(mid));//找到第一个左端点不小于mid的节点 
        if(it!=s.end()&&it->l==mid) return it;//如果这个节点左边就是mid则无需拆分 
        it--;//否则mid就在左边那个节点里面
        int L=it->l,R=it->r; 
        ll v=it->v;//存储左右边界和值 
        s.erase(it);//删除大区间 
        s.insert(Node(L,mid-1,v));
        return s.insert(Node(mid,R,v)).first;//加入小区间 
    }
    void assign(int l,int r,ll v)//区间赋值,合并大坑 
    {
        IT L=split(l),R=split(r+1);//拆分出[ l , r+1 )即 [ l , r ],R指向目标区间的下一个节点 
        s.erase(L,R);//删除这个范围内的所有区间 
        s.insert(Node(l,r,v));
    }
    void add(int l,int r,ll v)//区间加 
    {
        IT L=split(l),R=split(r+1);
        for(;L!=R;L++) L->v+=v;
    }
    ll rank(int l,int r,int k)//区间排名 
    {
        IT L=split(l),R=split(r+1);
        vector<pair<ll,int> > vc;//long long存值,int存区间长度 
        vc.clear();
        for(;L!=R;L++) vc.push_back(pair<ll,int>(L->v,L->r-L->l+1));
        sort(vc.begin(),vc.end());
        for(vector<pair<ll,int> >::iterator it=vc.begin();it!=vc.end();++it) 
        {
            k-=it->second;//如果减去这个区间之后排名不够了
            if(k<=0) return it->first;//就说明第k小就在这个区间 
        }
        return LLONG_MIN;
        //否则说明k已经大于整个区间长度,无解,也可以在函数开头判断 
    }
    ll qsum(int l,int r,ll v,ll mod)//区间幂次和 
    {
        IT L=split(l),R=split(r+1);
        ll ret=0;
        for(;L!=R;L++) ret=(ret+(ll)(L->r-L->l+1)*quickpow(L->v,v,mod))%mod;
        return ret;
    }
}

ll rnd()
{
    ll ret=seed;
    seed=(seed*7+13)%MOD;
    return ret;
}
ll a[N];

int main()
{
    cin>>n>>m>>seed>>vmax;
    for(int i=1;i<=n;++i) 
    {
        a[i]=(rnd()%vmax)+1;
        ODT::s.insert(ODT::Node(i,i,a[i]));//一个萝卜一个坑 
    }
    ODT::s.insert(ODT::Node(n+1,n+1,0));//因为右端点到n时会用到n+1,再加一个不影响结果的值 
    for(int i=1;i<=m;++i)
    {
        int op=(rnd()%4)+1;
        int l=(rnd()%n)+1;
        int r=(rnd()%n)+1;
        
        int x,y;
        if(l>r) swap(l,r);
        if(op==3) x=(rnd()%(r-l+1))+1;
        else x=(rnd()%vmax)+1;
        if (op==4) y=(rnd()%vmax)+1;//随机生成数据 
        
        if(op==1) ODT::add(l,r,ll(x));
        if(op==2) ODT::assign(l,r,ll(x));
        if(op==3) printf("%lld\n",ODT::rank(l,r,x));
        if(op==4) printf("%lld\n",ODT::qsum(l,r,ll(x),ll(y)));
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Chtholly/p/10393676.html