好Van的珂朵莉树

珂朵莉树

珂朵莉树的主要操作是区间覆盖,即将区间([l,r])全部染色为(c)

EXAMPLE

EXAMPLE 1

给出一个长度为(n)的序列,一共(q)次询问,每次询问给出(m)个区间,求这些区间并集的权值和。

(n leq 10^5,sum m leq 10^5)

SOLUTION 1

显然能用珂朵莉树做

珂朵莉树是一种基于std::set的暴力数据结构。

这个set维护若干区间,这些区间没有交集,且按左端点从小到大有序。

struct node {
	int l, r, c;
	int operator<(const node& a) const { return l < a.l; }
};
set<node> S;
typedef set<node>::iterator IT;

珂朵莉树的基本操作有两个:

  • split(pos)

这个操作把包含了(pos)位置的区间分成两段,分别为([l,pos-1])([pos,r]),且返回([pos,r])的指针。

写法也很简单:

IT split(int po) {
	IT it = S.lower_bound((node){po, -1, 0});
	if (it != S.end() && it->l == po) return it;
	--it;
	int l = it->l, r = it->r, c = it->c;
	S.erase(it);
	S.insert((node){l, po - 1, c});
	return S.insert((node){po, r, c}).first;
}
  • assign(l, r, c)

这个操作将被([l,r])包含的区间全部删除,然后用颜色(c)覆盖区间([l,r])

代码:

void cover(int l, int r, int c) {
	IT itr = split(r + 1), itl = split(l);
	S.erase(itl, itr); //意思是删除[itl, itr),注意左闭右开
	S.insert((node){l, r, c});
	reset(c, sum[r] - sum[l - 1]);
}

那么,对于上面那题,怎么用珂朵莉树解决?

对于每个询问,我们直接cover(l, r),由于操作的是整个区间,答案的增减可以直接(O(1))得到,代码只需要在上面的写法上变一下即可:

void cover(int l, int r, int c) {
	IT itr = split(r + 1), itl = split(l), it = itl;
	for (; it != itr; ++it) {
    	//这里可以直接根据it的左右端点增减答案
	}
	S.erase(itl, itr);
	S.insert((node){l, r, c});
	reset(c, sum[r] - sum[l - 1]);
}

EXAMPLE 2

给出一个(n)个节点的树以及(m)条树上路径,共(q)个询问,每次询问([l_i,r_i])这些路径的并的权值。

SOLUTION 2

先离线询问,把询问([l,r])挂在(r)。从左往右扫时,我们把第(i)条路径上的每个点染色为(i),然后遍历挂在(i)上的询问,设其左端点是(l),那么颜色(>=l)的点都有贡献。

树链剖分后路径染色转化为区间问题,为了统计(c>=l)的贡献和,我们再使用树状数组维护,用珂朵莉树解决染色问题,这题就搞定了。

复杂度

如果被覆盖的区间毫无贡献,那么可以证明珂朵莉树复杂度是(O(nlogn))的。

因为每次assign最多增加(3)个区间,每次增加是(O(logn))的,删除区间是均摊(O(logn))的,所以复杂度是(O(nlogn))的。


又扩展技能树啦~

原文地址:https://www.cnblogs.com/zjlcnblogs/p/11566816.html