珂朵莉树の由来
珂朵莉树(或称ODT(Old Driver Tree老司机树))
这个毒瘤算法由CodeForces - 896C Willem, Chtholly and Seniorious 的正解衍化而来
虽然很多时候在随机数据下跑时不错
由于其骗分暴力的非正统算法思想
一定要切记这只是骗分暴力,时间复杂度上并不正确
什么时候用珂朵莉树
珂朵莉树一般用来解决本来应当由线段树解决的区间类问题
而使得珂朵莉树暴力艹标程的关键是assign区间推平操作
也就是题目中必须有区间赋值操作,而且数据纯随机的情况下才有复杂度保证
珂朵莉树–初始化
一般珂朵莉树选择使用 set 维护数列
set中每个结点维护三个值
(
l
l
,
r
r
,
v
a
l
)
(ll,rr,val)
(ll,rr,val),意为
[
l
l
,
r
r
]
[ll,rr]
[ll,rr]内的值都为
v
a
l
val
val
也就是每个节点保存一段连续的值相同的区间
set中以每个区间左端点排序
struct node
{
int ll,rr;
mutable int val;
node(int L,int R=-1,int V=0): ll(L), rr(R), val(V) {}
bool operator < (const node& tt)const { return ll<tt.ll;}//以区间左端点排序
};
set<node> st;
需要注意的是
m
u
t
a
b
l
e
mutable
mutable
没有它对
v
a
l
val
val的修饰,在接下来add函数里导致CE。
接下来为方便有关迭代器的操作,我们做一个宏定义
#define IT set<node>::iterator
珂朵莉树–分裂Split
s
p
l
i
t
(
p
o
s
)
split(pos)
split(pos)操作是指将原来含有pos位置的节点分成两部分
[
l
,
p
o
s
−
1
]
[l,pos-1]
[l,pos−1]和
[
p
o
s
,
r
]
[pos,r]
[pos,r]
并返回分裂后以
p
o
s
pos
pos为左端点的结点的迭代器
IT split(int pos)
{
IT it=st.lower_bound(node(pos));//二分找到第一个左端点不小于pos的区间
if(it!=st.end()&&it->ll==pos) return it;//pos本身就是某个区间的左端点,不用分裂
--it;//否则上一个区间才是包含pos的区间
int ll=it->ll,rr=it->rr,val=it->val;
st.erase(it);//删除原结点
st.insert(node(ll,pos-1,val));
return st.insert(node(pos,rr,val)).first;//这里.first返回的是迭代器
}
珂朵莉树–区间赋值Assign
珂朵莉树就是靠这个东西维持其不正确的复杂度的
void assign(int ll,int rr,int val)
{
IT itr=split(rr+1),itl=split(ll);
st.erase(itl,itr);
st.insert(node(ll,rr,val));
}
分裂出需要的区间,删除后重新插入一个新的
注意分裂出
[
l
l
,
r
r
]
[ll,rr]
[ll,rr]区间时要先分裂右端点,在分裂左端点
e r a s e erase erase方法可以删除迭代器描述的区间 [ f i r s t , l a s t ) [first,last) [first,last),注意左闭右开
void erase (iterator first, iterator last)
在数据纯随机的情况下,可以证明每次
a
s
s
i
g
n
assign
assign的区间长度期望为
N
/
3
N/3
N/3
于是
s
e
t
set
set规模迅速下降,随后达到接近
O
(
m
l
o
g
n
)
O(mlogn)
O(mlogn)的玄学非正确复杂度
珂朵莉树–其他暴力操作
其他操作真的就是规规矩矩的纯暴力
直接取出对应区间,暴力对每一个进行操作
一般就是长这样
void fun(int ll,int rr)
{
IT itr=split(rr+1),itl=split(ll);
for(;itl!=itr;++itl)
{
//...
}
}
煮个栗子–区间求和
int qsum(int ll,int rr)
{
int res=0;
IT itr=split(rr+1),itl=split(ll);
for(;itl!=itr;++itl) res+=(itl->rr-itl->ll+1)*itl->val;//注意乘(itl->rr-itl->ll+1)
return res;
}
再煮个复杂点的栗子–区间Kth
也是一样的暴力
#define pir pair<int,int>//前一个记录值,后一个记录出现次数
int kth(int ll,int rr,int k)
{
vector<pir> vec;
IT itr=split(rr+1),itl=split(ll);
for(;itl!=itr;++itl)
vec.push_back( pir(itl->val,itl->rr-itl->ll+1) );
sort(vec.begin(),vec.end());//全部存下来排序就好
for(vector<pir>::iterator it=vec.begin();it!=vec.end();++it)
{
k-=it->second;
if(k<=0) return it->first;
}
return -1;
}
珂朵莉树–暴力AC应用
CodeForces - 896C Willem, Chtholly and Seniorious
毒瘤ODT的源头
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdio>
#include<set>
using namespace std;
typedef long long lt;
#define IT set<node>::iterator
#define pir pair<lt,int>
#define mkp(x,y) make_pair(x,y)
lt read()
{
lt f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
lt seed,vmax;
lt rnd()
{
lt res=seed;
seed=(seed*7+13)%1000000007;
return res;
}
const int maxn=100010;
int n,m;
lt a[maxn];
struct node
{
int ll,rr;
mutable lt val;
node(int L,int R=-1,lt V=0): ll(L), rr(R), val(V) {}
bool operator < (const node& tt)const { return ll<tt.ll;}
};
set<node> st;
lt qpow(lt a,lt k,lt p)
{
lt res=1; a%=p;
while(k>0){
if(k&1) res=(res*a)%p;
a=(a*a)%p; k>>=1;
}
return res;
}
IT split(int pos)
{
IT it=st.lower_bound(node(pos));
if(it!=st.end()&&it->ll==pos) return it;
--it;
int ll=it->ll,rr=it->rr;
lt val=it->val;
st.erase(it);
st.insert(node(ll,pos-1,val));
return st.insert(node(pos,rr,val)).first;
}
void assign(int ll,int rr,lt val)
{
IT itr=split(rr+1),itl=split(ll);
st.erase(itl,itr);
st.insert(node(ll,rr,val));
}
void add(int ll,int rr,lt val)
{
IT itr=split(rr+1),itl=split(ll);
for(;itl!=itr;++itl) itl->val+=val;
}
lt kth(int ll,int rr,int k)
{
vector<pir> vec;
IT itr=split(rr+1),itl=split(ll);
for(;itl!=itr;++itl)
vec.push_back(pir(itl->val,itl->rr-itl->ll+1));
sort(vec.begin(),vec.end());
for(vector<pir>::iterator it=vec.begin();it!=vec.end();++it)
{
k-=it->second;
if(k<=0) return it->first;
}
return -1;
}
lt qsum(int ll,int rr,lt x,lt y)
{
lt res=0;
IT itr=split(rr+1),itl=split(ll);
for(;itl!=itr;++itl)
res+=(qpow(itl->val,x,y)*((itl->rr-itl->ll+1)%y))%y,res%=y;
return res;
}
int main()
{
n=read();m=read();
seed=read();vmax=read();
for(int i=1;i<=n;++i)
{
a[i]=(rnd()%vmax)+1;
st.insert(node(i,i,a[i]));
}
for(int i=1;i<=m;++i)
{
int op=(rnd()%4)+1;
int ll=(rnd()%n)+1,rr=(rnd()%n)+1;
lt x,y;
if(ll>rr) swap(ll,rr);
if(op==3) x=(rnd()%(rr-ll+1))+1;
else x=(rnd()%vmax)+1;
if(op==4) y=(rnd()%vmax)+1;
if(op==1) add(ll,rr,x);
else if(op==2) assign(ll,rr,x);
else if(op==3) printf("%lld\n",kth(ll,rr,x));
else if(op==4) printf("%lld\n",qsum(ll,rr,x,y));
}
return 0;
}
洛谷P2572 [SCOI2010]序列操作
没什么好解释的。。毕竟就是暴力
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<set>
using namespace std;
#define IT set<node>::iterator
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=100010;
int n,m;
int a[maxn];
struct node
{
int ll,rr;
mutable int val;
node(int L,int R=-1,int V=0): ll(L), rr(R), val(V) {}
bool operator < (const node& tt)const { return ll<tt.ll;}
};
set<node> st;
IT split(int pos)
{
IT it=st.lower_bound(node(pos));
if(it!=st.end()&&it->ll==pos) return it;
--it;
int ll=it->ll,rr=it->rr,val=it->val;
st.erase(it);
st.insert(node(ll,pos-1,val));
return st.insert(node(pos,rr,val)).first;
}
void assign(int ll,int rr,int val)
{
IT itr=split(rr+1),itl=split(ll);
st.erase(itl,itr);
st.insert(node(ll,rr,val));
}
void rev(int ll,int rr)
{
IT itr=split(rr+1),itl=split(ll);
for(;itl!=itr;++itl) itl->val^=1;
}
int qsum(int ll,int rr)
{
int res=0;
IT itr=split(rr+1),itl=split(ll);
for(;itl!=itr;++itl) res+=(itl->rr-itl->ll+1)*itl->val;
return res;
}
int qmax(int ll,int rr)
{
int res=0,tt=0;
IT itr=split(rr+1),itl=split(ll);
for(;itl!=itr;++itl)
{
if(itl->val==0) res=max(res,tt),tt=0;
else tt+=itl->rr-itl->ll+1;
}
return max(res,tt);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;++i)
{
a[i]=read();
st.insert(node(i,i,a[i]));
}
for(int i=1;i<=m;++i)
{
int k=read(),ll=read()+1,rr=read()+1;
if(k==0) assign(ll,rr,0);
else if(k==1) assign(ll,rr,1);
else if(k==2) rev(ll,rr);
else if(k==3) printf("%lld\n",qsum(ll,rr));
else if(k==4) printf("%lld\n",qmax(ll,rr));
}
return 0;
}
洛谷P2787 语文1(chin1)- 理理思维 题解
时空消耗量都艹过正解不止一个数量级
再次提醒
不管珂朵莉树才实际中再怎么优秀,那都只是数据随机的结果
说到底只是暴力骗分手段
尽管可以用ODT AC很多题,但改变不了时间复杂度是非正确的的事实
建议平时的练习中少用,因为这样会忽略了题目本身正解的精髓
考场上对题目没有思路倒是一个很好的骗分手段