珂朵莉树(Chtholly Tree),一种基于std::set的暴力数据结构,是由某毒瘤在2017年的一场CF比赛中提出的数据结构,原名老司机树(Old Driver Tree,ODT)。由于第一个以珂朵莉树为正解的题目的背景以《末日时在做什么?有没有空?可以来拯救吗?》中的角色——珂朵莉为主角,所以这个数据结构又被称为珂朵莉树(Chtholly Tree)她主要处理区间修改、查询问题,在数据随机的前提下有着优秀的复杂度。
加了 assign
的珂朵莉树在随机数据下跑得飞快,节点数达到了 log 级别。也就是说,珂朵莉树的高效是由随机分配的Assign
保证的。
如果一个题目没有区间赋值操作或者有数据点没有赋值操作,或者数据很不随机,请不要把珂朵莉树当正解打。
珂朵莉树应用的范围很窄......
1.、Q:为什么要Split(r + 1)
?
A:便于取出 [l,r+1)[l,r+1) 的部分,即 [l,r][l,r]
2、Q:为什么要先Split(r + 1),
再Split(l)
?
A:因为如果先Split(l)
,返回的迭代器会位于所对应的区间以ll为左端点,此时如果rr也在这个节点内,就会导致Split(l)
返回的迭代器被erase
掉,导致RE。
3、Q:这些操作里面全是Split
,复杂度理论上会退化成暴力(不断分裂直到无法再分),怎么让它不退化?
A:assign操作。
题意:有n个元素,这n个元素最初的状态都为1,对n个元素做q次操作,每次操作把给定区间置0或置1,每次操作之后都要输出这n个元素中1的个数。
乍一看以为是线段树裸题,但是看到n的数据范围我放弃了......
网上看了博客,有三种方法来做这个题:1、珂朵莉树(ODT);2、set;3、线段树动态开点。
我看了用珂朵莉树的做法,听说珂朵莉树解决区间推平问题比较好用,然后就大概学了一下。
其实什么也没学懂,珂朵莉树的关键好像是split操作,然后其他的就比较暴力了。并且珂朵莉树的复杂度比较玄学,有些题目如果数据水的话能过,如果出题人专门弄数据卡你就玩完了,所以珂朵莉树不能当作正解......
推荐一个讲珂朵莉树的视频,里面也有这道题的大致讲解,好像是成电的大佬讲的:https://www.bilibili.com/video/av21651173from=search&seid=13578438704386102893
代码参考了下面这篇博客:https://www.cnblogs.com/bztMinamoto/p/9810833.html
下面这个代码,node 中 l , r , v 的含义表示区间 [ l , r ] 的和是 v 。
#include<bits/stdc++.h>
#define IT set<node>::iterator
using namespace std;
struct node
{
int l,r;
mutable int v;
node(int L,int R=-1,int V=0):l(L),r(R),v(V) {}
inline bool operator <(const node &b)const
{
return l<b.l;
}
};
set<node> s;
int sum;
//把pos所在的区间[l,r]分离成[l,pos-1]和[pos+1,r]
IT split(int pos)
{
IT it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos) return it;
--it;
int l=it->l,r=it->r,v=it->v;
s.erase(it);
s.insert(node(l,pos-1,v?pos-l:0));
return s.insert(node(pos,r,v?r-pos+1:0)).first;
}
//区间推平,把区间[l,r]的值置成v
void assign(int l,int r,int v)
{
IT itr=split(r+1),itl=split(l);
for(IT it=itl; it!=itr; ++it) sum-=it->v;
s.erase(itl,itr),s.insert(node(l,r,v*(r-l+1)));
sum+=v*(r-l+1);
}
int main()
{
int n,q;
scanf("%d%d",&n,&q);
s.insert(node(1,n,n));
sum=n;
int l,r,p;
while(q--)
{
scanf("%d%d%d",&l,&r,&p);
assign(l,r,p-1);
printf("%d\n",sum);
}
return 0;
}
题意:请你写一种奇怪的数据结构,支持:
- 11 ll rr xx :将[l,r][l,r] 区间所有数加上xx
- 22 ll rr xx :将[l,r][l,r] 区间所有数改成xx
- 33 ll rr xx :输出将[l,r][l,r] 区间从小到大排序后的第xx 个数是的多少(即区间第xx 小,数字大小相同算多次,保证 1\leq1≤ xx \leq≤ r-l+1r−l+1 )
- 44 ll rr xx yy :输出[l,r][l,r] 区间每个数字的xx 次方的和模yy 的值(即(\sum^r_{i=l}a_i^x∑i=lraix ) \mod ymody )
【输入格式】 这道题目的输入格式比较特殊,需要选手通过seedseed 自己生成输入数据。 输入一行四个整数n,m,seed,v_{max}n,m,seed,vmax (1\leq1≤ n,m\leq 10^{5}n,m≤105 ,0\leq seed \leq 10^{9}+70≤seed≤109+7 ,1\leq vmax \leq 10^{9},1≤vmax≤109 ) 其中n 表示数列长度,mm表示操作次数,后面两个用于生成输入数据。 题目中给出了生成数据的伪代码。
思路:珂朵莉树。
(这个题不知道为什么,用#define ll long long就报错,用typedef long long ll就行了,以前也遇到过这种情况,很奇怪)
下面这个代码,node 中 l , r , v 的含义表示区间 [ l , r ] 中每个数的值都是 v 。
#include<bits/stdc++.h>
#define Mod 1000000007
#define IT set<node>::iterator
typedef long long ll;
//#define ll long long
using namespace std;
ll a[100010];
struct node{
ll l,r;
mutable ll v;
node(ll L,ll R=-1,ll V=0):l(L),r(R),v(V){}
bool operator<(const node& p) const
{
return l<p.l;
}
};
set<node> s;
ll seed;
ll rnd()
{
ll ret=seed;
seed=(seed*7+13)%Mod;
return ret;
}
IT split(ll pos)
{
IT it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos)
return it;
--it;
ll l=it->l,r=it->r,v=it->v;
s.erase(it);
s.insert(node(l,pos-1,v));
return s.insert(node(pos,r,v)).first;
}
void assign(ll l,ll r,ll val)
{
IT itr=split(r+1),itl=split(l);
s.erase(itl,itr);
s.insert(node(l,r,val));
}
void add(ll l,ll r,ll val)
{
IT itr=split(r+1),itl=split(l);
for(IT it=itl;it!=itr;it++)
it->v+=val;
}
ll pow_mod(ll a,ll b,ll mod)
{
ll ret=1;
while(b)
{
if(b&1)
ret*=a;
a=a*a;
b>>=1;
}
return ret;
}
ll pown(ll a, ll b, ll mod)
{
ll res = 1;
ll ans = a % mod;
while (b)
{
if (b&1)
res = res * ans % mod;
ans = ans * ans % mod;
b>>=1;
}
return res;
}
ll sum(ll l, ll r, ll ex, ll mod)
{
IT itr = split(r+1),itl = split(l);
ll res = 0;
for (; itl != itr; ++itl)
res = (res + (ll)(itl->r - itl->l + 1) * pown(ll(itl->v), ll(ex), ll(mod))) % mod;
return res;
}
ll ask(ll l,ll r,ll k)
{
vector<pair<ll,ll> > vp;
IT itr=split(r+1),itl=split(l);
vp.clear();
for(;itl!=itr;itl++)
vp.push_back(pair <ll,ll> (itl->v,itl->r-itl->l+1));
sort(vp.begin(),vp.end());
for(vector<pair<ll,ll> >::iterator it=vp.begin();it!=vp.end();it++)
{
k-=it->second;
if(k<=0)
return it->first;
}
}
int main()
{
ll n,m;
ll vmax;
scanf("%I64d%I64d%I64d%I64d",&n,&m,&seed,&vmax);
ll op,l,r,x,y;
for (ll i=1; i<=n; ++i)
{
a[i] = (rnd() % vmax) + 1;
s.insert(node(i,i,a[i]));
}
for(ll i=1; i<=m; i++)
{
op=(rnd()%4)+1;
l=(rnd()%n)+1;
r=(rnd()%n)+1;
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)
add(l,r,x);
if(op==2)
assign(l,r,x);
if(op==3)
printf("%I64d\n",ask(l,r,x));
if(op==4)
printf("%I64d\n",sum(l,r,x,y));
}
return 0;
}
另外,推荐两篇关于珂朵莉树写得比较好的博客: