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

2026-07-26 08-28-54屏幕截图

  • 这个关键字赋予了变量可改的属性,我们就可以在 \(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分部分分

2026-07-28 14-52-56屏幕截图

其他操作

  • 其他的就很普通了,直接暴力干就完了,由于全随机数据和 \(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;,其实你可以手模一下检验正确性
  • 注意事项:
    1. 写多个 \(split\) 时要看好顺序,一个写错整题起飞
    2. 关于 \(4\) 的注意:不要把两个区间同时算完直接就清掉复制到的区间,会造成紊乱,应先遍历存储再做删除
    3. 关于 \(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] 镜中的昆虫

r16obpqh

  • 这种毒瘤题真是给人做吗

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 珂朵莉树的题找到的,发现之前收藏过于是就写

2026-07-27 20-27-14屏幕截图

  • 前置芝士:扫描线
  • 你同样可以看我之前写的
  • 我们看看题,发现居然没有珂朵莉和 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 分钟想出了上面的结论但是调了俩小时

  • 我们发现上面做法常数实在太大了,所以我们进一步扩展,发现一个进阶性质:区间与或上相邻的数相当于极长连续同色子段的扩散与收缩
  • 啥意思,我们看样例序列

2026-07-28 16-29-28屏幕截图

  • 执行一次 \(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的卡常记录:

2026-07-28 16-34-35屏幕截图

  • CE 是开 \(O3\) 炸了
  • 60 后的 30 是没卡过去
  • msjing以后(?)再写正解吧,咕咕咕

后话

  • msjing严肃决定下次回家研究珂学
  • msjing on 8.12:已严肃观看
posted @ 2026-07-26 10:58  msjing  阅读(24)  评论(0)    收藏  举报