Old Driver Tree(ODT 老驱动树)
,中文称为老驱动树,又名珂朵莉树(虽然我看到老驱动莫名想笑)
真是一个神奇的东西
但是只有在数据随机且有区间覆盖操作的时候才有用(因为只有区间覆盖才会容易产生一段相同的区间)
因为考虑到每次覆盖了一个区间之后整个区间的数都是一样的了
于是就将这个区间缩成一个点,用维护一下所有点
这时候我们就可以方便的对其操作
具体实现:
初始化
考虑到每个点都是一段相同的区间
所以要维护区间左右端点和区间的值
用结构体实现
#define IT set<node>::iterator
#define pi pair<ll,int>
#define mp(x,y) make_pair(x,y)
struct node{
int l,r;
mutable ll val;
node(int L,int R=-1,ll v=0):l(L),r(R),val(v){}
bool operator < (const node &a)const {return l<a.l;}
};
其主要操作就个:
看代码应该很好理解
将一段区间分成两段并返回后一段的开头,用作分离出一段特定区间来处理询问
inline IT split(int pos){
IT it=st.lower_bound(node(pos));
if(it!=st.end()&&it->l==pos)return it;
it--;
int l=it->l,r=it->r;
ll val=it->val;
st.erase(it);
st.insert(node(l,pos-1,val));
return st.insert(node(pos,r,val)).first;
}
用于处理区间覆盖,将一段区间提出来缩成一个点
inline void assign(int l,int r,ll val){
IT itr=split(r+1),itl=split(l);
st.erase(itl,itr);
st.insert(node(l,r,val));
}
另外几个操作(以CodeForces-896C为例)
:直接遍历中~中的所有点
inline void add(int l,int r,int val){
IT itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++)itl->val+=val;
}
查询区间第小:将区间所有点的信息加到一个内
按大小和数量遍历到第个输出就是了
inline ll kth(int l,int r,int k){
vector<pi>vec;
IT itr=split(r+1),itl=split(l);
for(;itl!=itr;itl++)
vec.push_back(mp(itl->val,itl->r-itl->l+1));
sort(vec.begin(),vec.end());
for(vector<pi>::iterator it=vec.begin();it!=vec.end();it++){
k-=it->second;
if(k<=0)return it->first;
}
return -1;
}
:区间所有数的次方之和
inline ll ksm(ll a,ll b,ll mod){
int res=1;a%=mod;
for(;b;b>>=1,a=a*a%mod){
if(b&1)res=res*a%mod;
}
return res;
}
inline ll sum(int l,int r,ll x,ll y){
ll res=0;
IT itr=split(r+1),itl=split(l);
for(;itl!=itr;++itl)
res+=(ksm(itl->val,x,y)*((itl->r-itl->l+1)%y))%y,res%=y;
return res;
}
感性理解一下复杂度 网上找不到证明
设区间数为
由初中数学可以证明每次的期望长度约为
不明白的可以自己百度
所以每次会使变成并有概率产生2个新的区间
开始的时候相当于每次减少
后面由于少了产生的新区间也会有贡献
然后大概就是了…
(赶紧逃)