ODT/珂朵莉树大学习
前言
在太阳西斜的这个世界里,置身天上之森。等这场战争结束后,不归之人与望眼欲穿的人们,人人本着正义之名。长存不灭的过去,逐渐消逝的未来。我回来了,纵使日薄西山,即便看不到未来,此时此刻的光辉,盼君勿忘。
a,我什么都不知道,因为这话是我粘过来的- 你发现了吗,每句话都是一道题
msjing on 26.7.27
- 如果你代码出现龟衣问题(TLE、RE、MLE)等,请将 \(split\) 操作的
if (it->r<x) return st.end();换成if (it->r<x) return ++it;
介绍
- 这个叫 \(Old Driver Tree\),又叫珂朵莉树,来源是CF896C,
其实这场比赛都是珂朵莉 - 我们下面的介绍都以这个题为例
- 这个东东基于 \(set\) 或 \(map\) 实现,当然也能用链表
- 本质上珂朵莉树并不是一种像线段树一样的 \(DS\),而是像分块一样的暴力数据结构,且也是一种思想,要求数据全随机要不然好像复杂度会掉,主要解决区间平推操作,本质思想是要求区间全改一个值,那我可以直接把区间当点处理
- 其实你看 CF896C,你会发现这题完全符合珂朵莉树的要求和优势:数据随机,区间推平
- 这个东东有什么用捏
骗分- 这个 \(DS\) 可以过一些随机生成的题,搞不好你在一些不太正规的模拟赛里能卡常卡过去
维护和操作
- 珂朵莉树的维护通常用 \(set\),这里介绍 \(set\)
- 不会 \(set\) 的请先去学一下不然可能晕针
- e,\(define\) 有点多可能有点影响阅读
define
#define int long long
#define ins insert
#define er erase
#define lb lower_bound
#define ub upper_bound
#define be begin
#define sit set<_>::iterator
#define fi first
#define se second
#define pb push_back
准备工作
- 算一个区间需要一个结构体,像线段树一样
- 先介绍一个关键字,e,我好像只在珂朵莉树见过
mutable

- 这个关键字赋予了变量可改的属性,我们就可以在 \(set\) 里直接改了
- 定义结构体为区间左端点,右端点,值,并按左端点重载小于号
点击查看代码
struct _
{
mutable int l,r,v;
bool operator < (const _ &a) const {return l<a.l;};
};
核心操作
- 珂朵莉树有两个核心操作:\(split\) 和 \(assign\)(分裂和推平)
split
- 分裂这个东西说白了就是一个点 \(x\),把区间分成 \([l,x-1]\) 和 \([x,r]\),然后把后面的指针返回
- 代码写的是卡常版,要不然你就要分两个,放两个,删原区间,吃三次 \(\log\),这样你只吃一次
点击查看代码
sit split(int x)
{
sit it=st.lb({x,0,0});
if (it!=st.end() && it->l == x) return it;
it--;
if (it->r<x) return st.end();
int r=it->r,v=it->v;
it->r=x-1;
return st.ins({x,r,v}).fi;
}
assign
- 我们上面说了分区间,下面用这个来分出要修的区间
- 我们考虑一个区间的修
- 我们可以把区间分为三块:修区间的左边,修区间的右边,修区间
- 我们就可以以 \(l\) 和 \(r+1\) 为节点分出这三个区间
- 注意先分 \(r+1\) 后分 \(l\),不然你的代码会出现神秘 \(RE\),至于为啥,e,msjing不知道喵
点击查看代码
void assign(int l,int r,int v)
{
sit it2=split(r+1),it1=split(l);
st.er(it1,it2);
st.ins({l,r,v});
}
卡常专用
void assign(int l,int r,int v)
{
sit it2=split(r+1),it1=split(l);
it1->r=r,it1->v=v;
s.er(++it1,it2);
}
msjing真靠这个卡过了30分部分分

其他操作
- 其他的就很普通了,直接暴力干就完了,由于全随机数据和 \(split\)、\(assign\) 保证了复杂度的正确
区间加
void add(int l,int r,int v)
{
sit it2=split(r+1),it1=split(l);
for (sit i=it1;i!=it2;i++) i->v+=v;
}
第 $k$ 小
int rnk(int l,int r,int k)
{
sit it2=split(r+1),it1=split(l);
vector<__> v;
for (sit i=it1;i!=it2;i++) v.pb({i->v,i->r-i->l+1});
sort(v.begin(),v.end());
int i;
for (i=0;i<v.size();i++)
{
if (k>v[i].len) k-=v[i].len;
else break;
}
return v[i].v;
}
算幂($x$ 记得先取模)
int power(int x,int y,int mo)
{
int res=1;
x%=mo;
while (y)
{
if (y&1) (res*=x)%=mo;
(x*=x)%=mo;
y>>=1;
}
return res;
}
int calc(int l,int r,int x,int y)
{
sit it2=split(r+1),it1=split(l);
int ans=0;
for (sit i=it1;i!=it2;i++) (ans+=power(i->v,x,y)*(i->r-i->l+1)%y)%=y;
return ans;
}
- 给全代码了喵
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ins insert
#define er erase
#define lb lower_bound
#define ub upper_bound
#define be begin
#define sit set<_>::iterator
#define fi first
#define se second
#define pb push_back
using namespace std;
constexpr int maxn=5e5+10,p=1e9+7;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,seed,vm;
int a[maxn];
int rnd()
{
int res=seed;
seed=(seed*7+13)%p;
return res;
}
struct _
{
mutable int l,r,v;
bool operator < (const _ &a) const {return l<a.l;};
};
struct __
{
int v,len;
bool operator < (const __ &a) const {return v<a.v;}
};
set<_> st;
sit split(int x)
{
sit it=st.lb({x,0,0});
if (it!=st.end() && it->l == x) return it;
it--;
if (it->r<x) return st.end();
int r=it->r,v=it->v;
it->r=x-1;
return st.ins({x,r,v}).fi;
}
void add(int l,int r,int v)
{
sit it2=split(r+1),it1=split(l);
for (sit i=it1;i!=it2;i++) i->v+=v;
}
void assign(int l,int r,int v)
{
sit it2=split(r+1),it1=split(l);
st.er(it1,it2);
st.ins({l,r,v});
}
int rnk(int l,int r,int k)
{
sit it2=split(r+1),it1=split(l);
vector<__> v;
for (sit i=it1;i!=it2;i++) v.pb({i->v,i->r-i->l+1});
sort(v.begin(),v.end());
int i;
for (i=0;i<v.size();i++)
{
if (k>v[i].len) k-=v[i].len;
else break;
}
return v[i].v;
}
int power(int x,int y,int mo)
{
int res=1;
x%=mo;
while (y)
{
if (y&1) (res*=x)%=mo;
(x*=x)%=mo;
y>>=1;
}
return res;
}
int calc(int l,int r,int x,int y)
{
sit it2=split(r+1),it1=split(l);
int ans=0;
for (sit i=it1;i!=it2;i++) (ans+=power(i->v,x,y)*(i->r-i->l+1)%y)%=y;
return ans;
}
signed main()
{
n=read(),m=read(),seed=read(),vm=read();
for (int i=1;i<=n;i++)
{
a[i]=(rnd()%vm)+1;
st.ins({i,i,a[i]});
}
for (int i=1;i<=m;i++)
{
int x=0,y=0;
int op=(rnd()%4)+1;
int l=(rnd()%n)+1;
int r=(rnd()%n)+1;
if (l>r) swap(l,r);
if (op == 3) x=(rnd()%(r-l+1))+1;
else x=(rnd()%vm)+1;
if (op == 4) y=(rnd()%vm)+1;
if (op == 1) add(l,r,x);
else if (op == 2) assign(l,r,x);
else if (op == 3) printf("%lld\n",rnk(l,r,x));
else printf("%lld\n",calc(l,r,x,y));
}
return 0;
}
不卡常版
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
constexpr int maxn = 1e5 + 10, p = 1e9 + 7;
long long read()
{
long long x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
return x * f;
}
struct _
{
long long l, r;
mutable long long v;
bool operator < (const _& a) const
{
return l < a.l;
}
_(long long l, long long r = 0, long long v = 0) : l(l), r(r), v(v) {}
};
long long n, m, seed, vm, a[maxn];
set <_> s;
set<_>::iterator split(int pos)
{
set<_>::iterator it = s.lower_bound(_(pos));
if (it != s.end() && it->l == pos) return it;
it--;
if (it->r < pos) return s.end();
long long l = it->l, r = it->r, v = it->v;
s.erase(it);
s.insert(_(l, pos - 1, v));
return s.insert(_(pos, r, v)).first;
}
void add(long long l, long long r, long long x)
{
set<_>::iterator itr = split(r + 1), itl = split(l);
for (set<_>::iterator it = itl; it != itr; it++) it->v += x;
}
void assign(long long l, long long r, long long x)
{
set<_>::iterator itr = split(r + 1), itl = split(l);
s.erase(itl, itr);
s.insert(_(l, r, x));
}
struct __
{
long long num, cnt;
bool operator < (const __& a) const
{
return num < a.num;
}
__(long long num, long long cnt) : num(num), cnt(cnt) {}
};
long long rnk(long long l, long long r, long long x)
{
set<_>::iterator itr = split(r + 1), itl = split(l);
vector<__> v;
for (set<_>::iterator i = itl; i != itr; i++) v.push_back(__(i->v, i->r - i->l + 1));
sort(v.begin(), v.end());
int i;
for (i = 0; i < v.size(); i++)
{
if (v[i].cnt < x) x -= v[i].cnt;
else break;
}
return v[i].num;
}
long long ksm(long long x, long long y, long long mo)
{
long long cnt = 1;
long long k = x % mo;
while (y)
{
if (y & 1) cnt = cnt * k % mo;
k = k * k % mo;
y >>= 1;
}
return cnt;
}
long long calP(long long l, long long r, long long x, long long y)
{
set<_>::iterator itr = split(r + 1), itl = split(l);
long long ans = 0;
for (set<_>::iterator i = itl; i != itr; i++) ans = (ans + ksm(i->v, x, y) * (i->r - i->l + 1) % y) % y;
return ans;
}
long long rnd()
{
long long res = seed;
seed = (seed * 7 + 13) % p;
return res;
}
int main()
{
n = read(), m = read(), seed = read(), vm = read();
for (int i = 1; i <= n; i++)
{
a[i] = (rnd() % vm) + 1;
s.insert(_(i, i, a[i]));
}
for (long long i = 1; i <= m; i++)
{
long long op, l, r, x, y;
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() % vm) + 1;
if (op == 4) y = (rnd() % vm) + 1;
if (op == 1) add(l, r, x);
else if (op == 2) assign(l, r, x);
else if (op == 3) printf("%lld\n",rnk(l, r, x));
else printf("%lld\n",calP(l, r, x, y));
}
return 0;
}
- 那个nb的格式化是 microsoft visual studio 干的
例题
P1840 Color the Axis
- 这个一眼是区间推平,直接珂朵莉树屮过去
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ins insert
#define er erase
#define lb lower_bound
#define ub upper_bound
#define be begin
#define sit set<_>::iterator
#define fi first
#define se second
#define pb push_back
using namespace std;
constexpr int maxn=5e5+10,p=1e9+7;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m;
struct _
{
mutable int l,r,v;
bool operator < (const _ &a) const {return l<a.l;};
};
set<_> st;
int ans;
sit split(int x)
{
sit it=st.lb({x,0,0});
if (it!=st.end() && it->l == x) return it;
it--;
if (it->r<x) return st.end();
int r=it->r,v=it->v;
it->r=x-1;
return st.ins({x,r,v}).fi;
}
void assign(int l,int r,int v)
{
sit it2=split(r+1),it1=split(l);
for (sit i=it1;i!=it2;i++) ans-=i->v*(i->r-i->l+1);
st.er(it1,it2);
st.ins({l,r,v});
}
signed main()
{
n=ans=read(),m=read();
st.insert({1,n+1,1});
for (int i=1;i<=m;i++)
{
int l=read(),r=read();
assign(l,r,0);
printf("%lld\n",ans);
}
return 0;
}
P2824 [HEOI2016/TJOI2016] 排序
- GGrun把这题放到 \(ACM\) 里了,改题改到了
- 我们发现共两个操作,直接排序会炸,就算你用桶排
- 那我们必须转化了
- 大家都说这是一个 \(trick\),具体是这样的
由于仅算一个值,所以二分很优秀,然后我们考虑如何二分,我们二分值,由于原序列排序后是一个有序数列,所以我们可以令序列中大于这个二分值的数记为 \(1\),其余记为 \(0\),我们就只需要对于每次操作排 \(0\) 和 \(1\) 就行了
- 我们接下来考虑如何操作,我们现在通过转化变成了 \(0/1\) 序列,那对于一个区间的数,不用关心他们的大小,仅需要关心与二分数的大小就行,这个挺好理解,因为这个数的大小仅会影响二分数的位置
- 那我们可以直接统计区间的 \(1\) 的个数,升序排序就直接把后段(\(1\) 的个数长)赋成 \(1\),其余赋成 \(0\),你发现这是一个推平,珂朵莉数可以直接干
- 就没了,核心在 \(trick\)
点击查看代码
#include<bits/stdc++.h>
#define sit set<_>::iterator
#define ins insert
#define er erase
#define lb lower_bound
#define ub upper_bound
#define be begin()
#define ed end()
#define fi first
#define se second
#define pb push_back
using namespace std;
constexpr int maxn=1e5+10,p=1e9+7;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
namespace ODT
{
struct _
{
mutable int l,r,v;
bool operator < (const _ &a) const {return l<a.l;};
};
set<_> s;
sit split(int x)
{
sit it=s.lb({x,0,0});
if (it!=s.ed && it->l == x) return it;
it--;
int r=it->r,v=it->v;
it->r=x-1;
return s.ins({x,r,v}).fi;
}
void work(int l,int r,int op)
{
// cerr << l << " " << r << " " << op << endl;
sit it2=split(r+1),it1=split(l);
int cnt=0;
for (sit i=it1;i!=it2;i++)
if (i->v)
cnt+=(i->r-i->l+1);
// cerr << cnt << endl;
s.er(it1,it2);
int k=r-l+1;
k-=cnt;
if (!op)
{
if (l<=l+k-1) s.ins({l,l+k-1,0});
if (l+k<=r) s.ins({l+k,r,1});
}
else
{
if (l<=l+cnt-1) s.ins({l,l+cnt-1,1});
if (l+cnt<=r) s.ins({l+cnt,r,0});
}
}
}using namespace ODT;
int n,m;
int a[maxn];
struct __ {int op,l,r;}q[maxn];
int pos;
int chk(int x)
{
// cerr << x << endl;
s.clear();
for (int i=1;i<=n;i++)
{
int k;
if (a[i]>=x) k=1;
else k=0;
// cerr << k << endl;
s.ins({i,i,k});
}
s.ins({n+1,n+1,1});
for (int i=1;i<=m;i++)
{
int op=q[i].op,l=q[i].l,r=q[i].r;
work(l,r,op);
}
// for (sit i=s.be;i!=s.ed;i++)
// cerr << i->l << " " << i->r << " " << i->v << endl;
sit it=s.lb({pos,0,0});
if (it!=s.ed && it->l == pos) return it->v;
it--;
return it->v;
}
int main()
{
n=read(),m=read();
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=m;i++)
{
int op=read(),l=read(),r=read();
q[i]={op,l,r};
}
pos=read();
// cerr << "---------------" << endl;
int l=1,r=n,ans=1;
while (l<=r)
{
// cerr << ans << endl;
// cerr << l << " " << r << endl;
int mid=(l+r) >> 1;
if (chk(mid)) l=mid+1,ans=mid;
else r=mid-1;
// cerr << l << " " << r << " " << ans << endl;
// cerr << "***********************" << endl;
}
// cerr << "-----------------------" << endl;
printf("%d\n",ans);
return 0;
}
/*
2 5
1 2
1 1 1
1 2 2
0 1 2
0 1 2
0 1 2
2
*/
/*ans1=2*/
/*
7 7
6 2 7 3 4 5 1
1 2 7
1 2 5
0 2 3
1 1 5
1 2 6
1 2 7
0 2 6
2
*/
/*ans2=2*/
P5350 序列
- 我们看操作,第二个是区间赋值,这不就是区间推平吗,再看一眼数据范围,诶woc这是啥
保证数据随机。
- 这这这直接上珂朵莉树屮
- 前仨好办,主要是后仨,接下来说说怎么维护
- 操作 \(4\) 区间复制:我们考虑如何将一个区间搞到另一个区间,我们先考虑凭空放,就是把遍历整个区间,每个块分着放,这个也一样,因为你把要修的区间删了后就是凭空放,接下来考虑放的位置,
我们发现新位置就是块左右的位置减去来的位置加上去的位置,e,给个代码理解一下:int l1=i->l-l+L,r1=i->r-r+R,v1=i->v;,其中,\(l\) 和 \(r\) 表示从哪 \(copy\) 的,\(L\) 和 \(R\) 表示 \(copy\) 到哪,然后就解决了,注意事项等会再说 - 操作 \(5\) 区间交换:和区间复制相似,只不过现在你要删两个加两个,但是细节有点多,还是等会说
- 操作 \(6\) 区间翻转:这个东西我本来想的是从中间分开区间,然后反向扫两段,再做区间复制
但是假了,其实,我们知道一段区间的翻转仅仅是左右端点改变,值是不变的,所以我们考虑直接改端点,块的左端点是区间左右端点加和减去区间右端点,左端点相反,还是给个代码方便理解:int l=L+R-(i->r),r=L+R-(i->l),v=i->v;,其实你可以手模一下检验正确性 - 注意事项:
- 写多个 \(split\) 时要看好顺序,一个写错整题起飞
- 关于 \(4\) 的注意:不要把两个区间同时算完直接就清掉复制到的区间,会造成紊乱,应先遍历存储再做删除
- 关于 \(5\) 的注意:换区间还是上面说的,不要一起处理,因为你需要处理一个删一个,还有就是处理的顺序,非常坑,一定要写对不然大红大紫
- 其他看看代码就理解了,尤其是上面说的注意一定要在代码里好好看
- 由于随机数据和区间推平的存在,复杂度可以保证
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ins insert
#define er erase
#define lb lower_bound
#define ub upper_bound
#define be begin
#define sit set<_>::iterator
#define fi first
#define se second
#define pb push_back
using namespace std;
constexpr int maxn=5e5+10,p=1e9+7;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m;
int a[maxn];
struct _
{
mutable int l,r,v;
bool operator < (const _ &a) const {return l<a.l;};
};
set<_> st;
sit split(int x)
{
sit it=st.lb({x,0,0});
if (it!=st.end() && it->l == x) return it;
it--;
if (it->r<x) return st.end();
int r=it->r,v=it->v;
it->r=x-1;
return st.ins({x,r,v}).fi;
}
void assign(int l,int r,int v)
{
sit it2=split(r+1),it1=split(l);
st.er(it1,it2);
st.ins({l,r,v});
}
int sum(int l,int r)
{
int res=0;
sit it2=split(r+1),it1=split(l);
for (sit i=it1;i!=it2;i++) (res+=(i->v)*(i->r-i->l+1))%=p;
return res;
}
void add(int l,int r,int v)
{
sit it2=split(r+1),it1=split(l);
for (sit i=it1;i!=it2;i++) (i->v+=v)%=p;
}
void cpy(int l,int r,int L,int R)
{
sit it2=split(r+1),it1=split(l);
vector<_> s;
for (sit i=it1;i!=it2;i++)
{
int l1=i->l-l+L,r1=i->r-r+R,v1=i->v;
s.pb({l1,r1,v1});
}
sit it4=split(R+1),it3=split(L);
st.er(it3,it4);
for (auto i:s) st.ins(i);
}
void swp(int l,int r,int L,int R)
{
if (l>L) swap(l,L),swap(r,R);
sit it2=split(r+1),it1=split(l);
vector<_> s1,s2;
for (sit i=it1;i!=it2;i++)
{
int l1=i->l-l+L,r1=i->r-r+R,v1=i->v;
s1.pb({l1,r1,v1});
}
st.er(it1,it2);
sit it4=split(R+1),it3=split(L);
for (sit i=it3;i!=it4;i++)
{
int l1=i->l-L+l,r1=i->r-R+r,v1=i->v;
s2.pb({l1,r1,v1});
}
st.er(it3,it4);
for (auto i:s1) st.ins(i);
for (auto i:s2) st.ins(i);
}
void tun(int L,int R)
{
sit it2=split(R+1),it1=split(L);
vector<_> s;
for (sit i=it1;i!=it2;i++)
{
int l=L+R-(i->r),r=L+R-(i->l),v=i->v;
s.pb({l,r,v});
}
st.er(it1,it2);
for (auto i:s) st.ins(i);
}
void putout()
{
for (sit i=st.begin();i!=st.end();i++)
for (int j=i->l;j<=i->r;j++) printf("%lld ",i->v);
putchar('\n');
}
signed main()
{
n=read(),m=read();
for (int i=1;i<=n;i++)
a[i]=read(),st.ins({i,i,a[i]});
while (m--)
{
int op=read();
if (op == 1)
{
int l=read(),r=read();
printf("%lld\n",(sum(l,r)+p)%p);
}
else if (op == 2)
{
int l=read(),r=read(),v=read();
assign(l,r,v);
}
else if (op == 3)
{
int l=read(),r=read(),v=read();
add(l,r,v);
}
else if (op == 4)
{
int l=read(),r=read(),L=read(),R=read();
cpy(l,r,L,R);
}
else if (op == 5)
{
int l=read(),r=read(),L=read(),R=read();
swp(l,r,L,R);
}
else
{
int l=read(),r=read();
tun(l,r);
}
// putout();
}
putout();
return 0;
}
- 当然,你也可以像我这样写个 \(output\) 来实时检查操作的正确性,我是靠这个找到我区间翻转写假的
诶我是不是 define 了个 begin 但是没用a
P4690 [Ynoi Easy Round 2016] 镜中的昆虫

- 这种毒瘤题真是给人做吗
lxl orz!
- 前置芝士:\(CDQ\) 分治
- 不会的可以去我之前写的那看
- 参考题解,讲的非常好,思路很清晰
msjing写珂朵莉树写炸了于是直接去看如何处理重构了- 我们
玩gal看题 - 第一个操作是区间推平,woc这这这不珂朵莉树吗直接屮
- 结局
这件事情告诉我们别在数据非全随机的情况下用珂朵莉树乱搞- 我们考虑这个问题,我们发现第一个操作的确珂朵莉树可以方便地创过去,但是第二个不太行,我们考虑一个新解法
- 我们想想区间数不同颜色段,这个可以通过记上一次这个颜色出现的位置来利用扫描线创过去,带上修还是差不多,但是不能再用扫描线来创了,因为修改操作有序,因此这个问题便成了高维偏序,就得上 \(CDQ\) 分治了
- 然后思路就很清晰了,先用珂朵莉树高效处理,然后就直接离线 \(CDQ\) 计算
- 但是毕竟是 Ynoi,不会让你过得这么舒服,就有一万个细节要注意,
msjing直接糖丸忘了 split 也要重构 + BIT 写假 - 珂朵莉树的实现msjing真不会,建议大家去原题解看看,我代码乱给了
点击查看代码
#include<bits/stdc++.h>
#define ins insert
#define er erase
#define lb lower_bound
#define ub upper_bound
#define be begin()
#define ed end()
#define sit set<_>::iterator
#define fi first
#define se second
#define pb push_back
using namespace std;
constexpr int maxn=1e5+10,p=1e9+7;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m;
int a[maxn],lt[maxn*2];
int num;
map<int,int> mp;
int tt;
struct __ {int l,r,t,v,id;}q[maxn*15];
int qt;
struct _
{
mutable int l,r,v;
bool operator < (const _ &a) const {return l<a.l;}
};
int pre[maxn*2];
set<_> st,col[maxn*2];
sit Ins(int l,int r,int v)
{
col[v].ins({l,r,v});
return st.ins({l,r,v}).fi;
}
void Del(int l,int r,int v)
{
col[v].er({l,r,v});
st.er({l,r,v});
}
int pr(int x)
{
sit it=st.ub({x,0,0});
it--;
if (it->l<x) return x-1;
else
{
sit c=col[it->v].lb({x,0,0});
if (c!=col[it->v].be) return (--c)->r;
return 0;
}
}
sit split(int x)
{
sit it=st.lb({x,0,0});
if (it!=st.ed && it->l == x) return it;
it--;
if (it->r<x) return st.ed;
int l=it->l,r=it->r,v=it->v;
Del(l,r,v);
Ins(l,x-1,v);
return Ins(x,r,v);
}
void assign(int l,int r,int v,int t)
{
sit it2=split(r+1),it1=split(l);
vector<int> s;
for (sit i=it1;i!=it2;i++)
{
if (i!=it1) s.pb(i->l);
sit nt=col[i->v].ub(*i);
if (nt!=col[i->v].ed) s.pb(nt->l);
col[i->v].er(*i);
}
st.er(it1,it2);
Ins(l,r,v);
s.pb(l);
sit nt=col[v].ub({l,r,v});
if (nt!=col[v].ed) s.pb(nt->l);
for (auto i:s)
{
q[++qt]={i,pre[i],t,-1,0};
pre[i]=pr(i);
q[++qt]={i,pre[i],t,1,0};
}
}
#undef ins
#undef er
#undef lb
#undef ub
#undef be
#undef ed
#undef sit
#undef fi
#undef se
struct BIT
{
int tr[maxn];
int lb(int x) {return x&(-x);}
void add(int x,int v) {while (x<=maxn-10) tr[x]+=v,x+=lb(x);}
int que(int x)
{
int res=0;
while (x) res+=tr[x],x-=lb(x);
return res;
}
}BT;
long long ans[maxn];
int cnt;
void cdq(int l,int r)
{
if (l == r) return;
int mid=(l+r) >> 1;
cdq(l,mid),cdq(mid+1,r);
sort(q+l,q+mid+1,[](__ a,__ b){return a.l!=b.l?a.l<b.l:a.id<b.id;});
sort(q+mid+1,q+r+1,[](__ a,__ b){return a.l!=b.l?a.l<b.l:a.id<b.id;});
int i=l,j=mid+1;
while (j<=r)
{
while (i<=mid && q[i].l<=q[j].l)
{
if (!q[i].id) BT.add(q[i].r+1,q[i].v);
i++;
}
if (q[j].id) ans[q[j].id]+=BT.que(q[j].r+1)*q[j].v;
j++;
}
for (int P=l;P<i;P++) if (!q[P].id) BT.add(q[P].r+1,-q[P].v);
}
int main()
{
// freopen("ftc3.in","r",stdin);
// freopen("787878.out","w",stdout);
n=read(),m=read();
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=n;i++)
{
if (!mp[a[i]]) mp[a[i]]=++tt;
a[i]=mp[a[i]];
pre[i]=lt[a[i]];
lt[a[i]]=i;
q[++qt]={i,pre[i],0,1,0};
Ins(i,i,a[i]);
}
for (int i=1;i<=m;i++)
{
int op=read();
if (op == 1)
{
int l=read(),r=read(),v=read();
if (!mp[v]) mp[v]=++tt;
v=mp[v];
assign(l,r,v,i);
}
else
{
int l=read(),r=read();
q[++qt]={r,l-1,i,1,++num};
q[++qt]={l-1,l-1,i,-1,num};
}
}
sort(q+1,q+1+qt,[](__ &a,__ &b){return a.t!=b.t?a.t<b.t:a.id<b.id;});
cdq(1,qt);
for (int i=1;i<=num;i++) printf("%lld\n",ans[i]);
return 0;
}
P8512 [Ynoi Easy Round 2021] TEST_152
- 这题是 Super_lollipop 推荐给msjing的但是当时msjing在搞 \(CDQ\) 就没写,是 rand 珂朵莉树的题找到的,发现之前收藏过于是就写

- 前置芝士:扫描线
- 你同样可以看我之前写的
- 我们看看题,发现
居然没有珂朵莉和 gal 了不是Ynoi的作风a有一个显然的区间推平,于是你直接用珂朵莉树暴力干 - 结局2.0
- 然后你发现你的推平太慢了,考虑搞一下这里
- 我们想想答案是怎么更新的,当一段区间被推平时,原区间贡献消失,新区间产生贡献,画个图
算了 tldraw 加载太慢了不画了- 所以我们可以在更新时直接减去原贡献,加上新贡献
- 考虑如何区间操作
- 我们想到可以利用扫描线来维护,仍然是扫描线的基本操作,右端点排序扫左边,离线做差分求解
- 但是我们发现操作有顺序,所以我们可以再在珂朵莉树里记一维,记录时间,每次在推平时在原序列原时间处撤销贡献,再加上现贡献
- 时间复杂度分析看这里,讲的很清晰,/bx
- 不过有一个什么很坑的点,就是我第二个二级标题下说的,你没找到 \(x\) 返回 \(end\) 会掉一个点,挺神奇
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ins insert
#define er erase
#define lb lower_bound
#define ub upper_bound
#define be begin()
#define ed end()
#define sit set<_>::iterator
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define getchar getchar_unlocked
using namespace std;
constexpr int maxn=1e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,k;
struct __ {int l,r,v;}q[maxn];
struct BIT
{
int c[maxn];
int lowbit(int x) {return x&(-x);}
void add(int x,int v) {if (!x) return;while (x<=n) c[x]+=v,x+=lowbit(x);}
int que(int x)
{
int res=0;
while (x) res+=c[x],x-=lowbit(x);
return res;
}
}BT;
struct _
{
mutable int l,r,v,t;
bool operator < (const _ &a) const {return l<a.l;}
};
set<_> s;
sit split(int x)
{
sit it=s.lb({x,0,0,0});
if (it!=s.ed && it->l == x) return it;
it--;
if (it->r<x) return ++it;
int r=it->r,v=it->v,t=it->t;
it->r=x-1;
return s.ins({x,r,v,t}).fi;
}
void assign(int l,int r,int v,int t)
{
sit it2=split(r+1),it1=split(l);
for (sit i=it1;i!=it2;i++) BT.add(i->t,-(i->r-i->l+1)*(i->v));
s.er(it1,it2);
BT.add(t,(r-l+1)*v);
s.ins({l,r,v,t});
}
vector<pii> op[maxn];
int ans[maxn];
signed main()
{
n=read(),m=read(),k=read();
s.ins({1,n,0});
for (int i=1;i<=n;i++)
{
int l=read(),r=read(),v=read();
q[i]={l,r,v};
}
for (int i=1;i<=k;i++)
{
int l=read(),r=read();
op[r].pb({l,i});
}
for (int i=1;i<=n;i++)
{
assign(q[i].l,q[i].r,q[i].v,i);
for (auto j:op[i])
ans[j.se]=BT.que(i)-BT.que(j.fi-1);
}
for (int i=1;i<=k;i++) printf("%lld\n",ans[i]);
return 0;
}
P5066 [Ynoi Easy Round 2014] 人人本着正义之名
- 你可能好奇为什么这个东西在这里
- 其实正解msjing并不会,因为:
泥马的 \(FHQ\) 上打 \(tag\)
- 显然msjing的平衡树只能过板子
- 但是你可以用珂朵莉树暴力拿 60 分,非常爽
- 所以仅介绍 60 分暴力
- 我们观察序列,发现这个东西根本维护不了
- 我们尝试找一找性质,发现一个很好玩的性质
\(0\) \(|\) \(0 = 0\)
\(0\) \(|\) \(1 = 1\)
\(1\) \(|\) \(9 = 1\)
\(1\) \(|\) \(1 = 1\)
\(0\) & \(0 = 0\)
\(0\) & \(1 = 0\)
\(1\) & \(0 = 0\)
\(1\) & \(1 = 1\)
- sbmqwm:这个玩意不是显然吗
- 但是我们观察到当与或位运算的两边相同时,值与前面一样
- (其实sbmqwm仅仅是被msjing拉过来做路人的喵)
- 因此我们注意到一个块内数是不会变的,仅需要处理交界
- 因此我们在进行 \(3\) ~ \(6\) 操作时,先把修区间找到,存储区间信息,删除原区间,判断每个边界,记录新块,加入 \(set\),这样写很显然但是就是常数极大
sbmqwm:msjing 5 分钟想出了上面的结论但是调了俩小时
- 我们发现上面做法常数实在太大了,所以我们进一步扩展,发现一个进阶性质:区间与或上相邻的数相当于极长连续同色子段的扩散与收缩
- 啥意思,我们看样例序列

- 执行一次 \(3\) 操作后,最右边的 \(1\) 段向左延伸了 \(1\),而最长的 \(0\) 段向左收缩了 \(1\)
- 因此我们就可以写出常数小的珂朵莉树了
- 这个性质也是正解的基础,
实现另说 - msjing没推出进阶的性质,所以按第一个实现的,常数极大代码极史调试还多
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ins insert
#define er erase
#define lb lower_bound
#define ub upper_bound
#define be begin()
#define ed end()
#define sit set<_>::iterator
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define getchar getchar_unlocked
using namespace std;
constexpr int maxn=3e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m;
struct _
{
mutable int l,r,v;
bool operator < (const _ &a) const {return l<a.l;};
};
set<_> s;
sit split(int x)
{
sit it=s.lb({x,0,0});
if (it!=s.ed && it->l == x) return it;
it--;
if (it->r<x) return s.ed;
int r=it->r,v=it->v;
it->r=x-1;
return s.ins({x,r,v}).fi;
}
void assign(int l,int r,int v)
{
sit it2=split(r+1),it1=split(l);
it1->r=r,it1->v=v;
s.er(++it1,it2);
}
void Upd(int l,int r,int op)
{
sit it2=split(r+1),it1=split(l);
vector<_> p,q;
for (sit i=it1;i!=it2;i++) p.pb(*i);
s.er(it1,it2);
if (op == 3 || op == 5)
{
for (int i=0;i<p.size()-1;i++)
{
_ a=p[i],b=p[i+1];
// cerr << a.l << " " << a.r << " " << a.v << endl;
// cerr << b.l << " " << b.r << " " << b.v << endl;
if (a.v == b.v) q.pb(a);
else
{
if (op == 3)
{
if ((a.v|b.v) == a.v){
// cerr << "=:";
// cerr << a.l < " " << a.r << " " << a.v << endl;
q.pb(a);
}
else
{
if (a.l == a.r) {
// cerr < ""
q.pb({a.l,a.r,a.v^1});
}
else q.pb({a.l,a.r-1,a.v}),q.pb({a.r,a.r,a.v^1});
}
}
else
{
if ((a.v&b.v) == a.v) q.pb(a);
else
{
if (a.l == a.r) q.pb({a.l,a.r,a.v^1});
else q.pb({a.l,a.r-1,a.v}),q.pb({a.r,a.r,a.v^1});
}
}
}
}
s.ins(p[p.size()-1]);
}
else
{
for (int i=1;i<p.size();i++)
{
_ a=p[i],b=p[i-1];
// cerr << a.l << " " << a.r << " " << a.v << endl;
// cerr << b.l << " " << b.r << " " << b.v << endl;
if (a.v == b.v) q.pb(a);
else
{
if (op == 4)
{
if ((a.v|b.v) == a.v) q.pb(a);
else
{
if (a.l == a.r) q.pb({a.l,a.r,a.v^1});
else q.pb({a.l+1,a.r,a.v}),q.pb({a.l,a.l,a.v^1});
}
}
else
{
if ((a.v&b.v) == a.v) q.pb(a);
else
{
if (a.l == a.r) q.pb({a.l,a.r,a.v^1});
else q.pb({a.l+1,a.r,a.v}),q.pb({a.l,a.l,a.v^1});
}
}
}
}
s.ins(p[0]);
}
for (auto i:q) s.ins(i);
}
int que(int l,int r)
{
sit it2=split(r+1),it1=split(l);
int res=0;
for (sit i=it1;i!=it2;i++)
res+=(i->v)*(i->r-i->l+1);
return res;
}
int ans;
signed main()
{
n=read(),m=read();
for (int i=1;i<=n;i++)
{
int x=read();
s.ins({i,i,x});
}
while (m--)
{
int op=read(),l=read()^ans,r=read()^ans;
if (op == 1) assign(l,r,0);
else if (op == 2) assign(l,r,1);
else if (op == 7) ans=que(l,r),printf("%lld\n",ans);
else Upd(l,r,op);
// cerr << op << ":";
// for (sit i=s.be;i!=s.ed;i++)
// for (int j=i->l;j<=i->r;j++) cerr << i->v << " ";
// cerr << endl;
}
return 0;
}
- msjing的卡常记录:

- CE 是开 \(O3\) 炸了
- 60 后的 30 是没卡过去
- msjing以后(?)再写正解吧,咕咕咕
后话
- msjing严肃决定下次回家研究珂学
- msjing on 8.12:已严肃观看

浙公网安备 33010602011771号