珂朵莉树 ODT

主要内容

珂朵莉树是基于数据随机且有整体赋值操作而对序列操作的乱搞算法。

它的主要思想是用 set 维护若干个数值上相同的区间,并暴力处理其他询问。

建立

set 中,我们需要用结构体记录每个区间的信息:

struct ODT
{
    int l,r;
    mutable ll val; // mutable 表示可变的,便于在外边修改 val 的值
    ODT(int L,int R=-1,ll Val=0):l(L),r(R),val(Val){} // 相当于 (ODT){l,r,val}
    bool friend operator < (ODT x,ODT y) { return x.l<y.l; } // 重载 <
};
set<ODT> s;

这样再 set 中就可以自动按照左端点排序了,注意在初始化的时候加入 \([n+1,n+1]\) 的区间。

split

\(\color{yellow}{\bigstar\texttt{Attention}}\):不要在定义函数的时候用 autoauto 使用必须规定类型,不然会 CE!!!!

珂朵莉树用 split 操作分裂出一堆区间后暴力操作。

// inline std::set<ODT>::operator split(int p) ?????
inline auto split(int p)
{
    auto it=s.lower_bound(ODT(p));
    if(it!=s.end() && it->l==p) return it;
    it--;
    int l=it->l,r=it->r;
    ll v=it->val;
    s.erase(it);
    s.insert(ODT(l,p-1,v));
    return s.insert(ODT(p,r,v)).first;
}

assign

如果只有 split 操作那 set 中元素岂不是爆炸?我们需要时不时将一些区间合并为一个区间才能保证复杂度。

\(\color{yellow}{\bigstar\texttt{Attention}}\):在推平的时候需要先 split 右端点再左端点,否则会 RE!

inline void assign(int l,int r,int v)
{
    auto itr=split(r+1),itl=split(l);
    s.erase(itl,itr); // 用将 [itl,itr) 之间的元素全部删除
    s.insert(ODT(l,r,v));
}

其他操作

其他操作就可以直接暴力在 set 中搞了,下面以求区间内每个数的平方的和为例:

inline ll query(int l,int r)
{
    auto itr=split(r+1),itl=split(l);
    ll ret=0;
    for(auto it=itl;it!=itr;it++)
        ret+=1ll*(it->r-it->l+1)*it->val*it->val;
    return ret;
}

复杂度

如果只有推平操作,即不需要将这个区间拎出来暴力处理,复杂度是 \(\mathcal{O(n\log n)}\)

如果有区间加等操作,就要基于数据随机了,这种情况下一定时间内进行一次推平可以达到 \(\mathcal{O(m\log n)}\) 的复杂度。

例题

现在,你已经对 ODT 的基本原理有了一定的了解,就让我们来看一看下面这个简单的例子,来吧我们刚刚学到的知识运用到时间中吧。

试试看!(bushi

起源题:CF896C Willem, Chtholly and Seniorious

CF1638E Colorful Operations

在询问的时候把答案累加上去,再套上一个树状数组利用前缀和维护区间加即可。

CF1423G Growing flowers

给你一个序列,支持以下两种操作:

  • 区间推平,即将 \([l,r]\) 中的数都改为同一个数 \(x\)
  • 求序列中所有长度为 \(k\) 的子段中不同数的个数的和。

\(n,q\le 10^5\)

MatrixCascade:这题 800。

区间推平,所以考虑珂朵莉树。发现如果每次询问的时候再去统计,复杂度至少是和区间个数同阶,不可取。

考虑在推平的过程中更新答案。

将即将被推平的区间拎出来,将他们对答案减小的贡献减去。

对每一种数建立一个 set,再用线段树记录方案数即可。

P8441 旭日东升

维护一个不可重集合的序列 \(a\),长度为 \(n\)。支持以下两种操作:

  • l r x 对于每个 \(l\le i\le r\),将 \(x\) 并入 \(a_i\)
  • l r\(S\) 把每个 \(l\le i\le r\)\(a_i\) 并在一起的集合,输出 \(S\) 中所有元素的和。

\(n,m,x\le 10^5,1\le l\le r\le n\)

ODT 的常见运用,开 \(10^5\)set,维护区间中是否有这个数。

注意要查询时不能 split 区间,我们必须保证不存在连续的 \(0\) 区间。

(详见 暑假颓废记录

P4690 [Ynoi2016] 镜中的昆虫

维护一个长为 \(n\) 的序列 \(a_i\),有 \(m\) 次操作。

  • 1 l r x 将区间 \([l,r]\) 的值修改为 \(x\)
  • 2 l r 询问区间 \([l,r]\) 出现了多少种不同的数,也就是说同一个数出现多次只算一个。

\(1\le n,m\le 10^5,1\le a_i\le 10^9\)

和上一题不能说差不多,只能说十分相似(bushi

用一个 ODT 维护全局颜色端,对每个颜色再分别维护颜色端。

用矩形加、单点查的套路做,分治解决即可。

posted @ 2022-03-24 11:57  EricQian06  阅读(149)  评论(0)    收藏  举报