其他分享
首页 > 其他分享> > 珂朵莉树

珂朵莉树

作者:互联网

珂朵莉树(\(ODT\))

0x01 珂朵莉树可以解决什么问题

对于涉及区间推平操作的问题,就是把\([l, r]\)区间内的所有数字变成相同的一个数。如果数据是随机的就可以使用珂朵莉树。

0x02 珂朵莉树的基本原理

比如一开始把一个区间分成这些部分,那么我们就可以用一个结构体将相邻的数字存起来,分别记录他们的始末点和这一段区间内的数字是多少。

struct Node {
    ll l, r;//l和r表示这一段的起点和终点
    mutable ll v;//v表示这一段上所有元素相同的值是多少

    Node(ll l, ll r = 0, ll v = 0) : l(l), r(r), v(v) {}

    bool operator<(const Node &a) const {
        return l < a.l;//规定按照每段的左端点排序
    }
};

关键字\(mutable\) 的意思是即使是一个常数,也允许修改\(v\)的值
将结构体存储在\(set\)里面按照左端点进行排序,就变成了下图的样子。

0x03 核心函数

\(split 分裂\)

因为我们要对一个区间内的所有数字进行操作,那么我们只需要将这个区间从原来的区间中取出来修改好了之后再放回去,就可以高效的实现区间推平操作了。
我们按照\(pos\)这个位置来进行分裂,将\([l, r]\)区间分裂成\([l, pos - 1], [pos, r]\)两个区间。如果\(pos\)本身就是一个区间的开头,那么就不需要去分割了直接返回这个区间就可以了。

set<Node>::iterator split(int pos) {
    set<Node>::iterator it = s.lower_bound(Node(pos));
    if (it != s.end() && it->l == pos) {
        return it;
    }
    it--;
    if (it->r < pos) return s.end();
    ll l = it->l;
    ll r = it->r;
    ll v = it->v;
    s.erase(it);
    s.insert(Node(l, pos - 1, v));
    //insert函数返回pair,其中的first是新插入结点的迭代器
    return s.insert(Node(pos, r, v)).first;
}

将区间取出来之后,我们要确定我们找到的这个\(it\)是刚好是在以\(pos\)开头的这个区间,还是\(pos\)稍微大了一点点或者\(pos\)过大超出了最后一个区间。如果是第一种情况的话就可以直接返回这个区间,如果是后两种情况的话,可以先将\(it\)这个迭代器向前退一个单位,然后这个时候看看区间的右端点和\(pos\)的大小关系,如果小于\(pos\)的话,那么就说明\(pos\)太大了,直接返回\(s.end()\),否则的话就将区间一分为二再重新插入回去。

\(assign 推平\)

如图,我们将这个区间取出之后,将\(Node结构体\)重新插入回去,就实现了。

void assign(ll l, ll r, ll x) {
    set<Node>::iterator itr = split(r + 1), itl = split(l);
    s.erase(itl, itr);
    s.insert(Node(l, r, x));
}

当然,这个时候一定要先将区间的右端点取出来再将左端点取出来,否则可能会出现\(RE\)的情况。

\(add\)修改操作

就是将区间分裂之后,循环遍历一遍区间,将所有的数都加上\(x\)就好了。

void add(ll l, ll r, ll x) {
    set<Node>::iterator itr = split(r + 1), itl = split(l);
    for (set<Node>::iterator it = itl; it != itr; ++it) {
        it->v += x;
    }
}

0x04整体的模板

struct ODT {
	struct Node {
		int64_t l, r;
		mutable int64_t v;

		Node (int64_t l, int64_t r = 0, int64_t v = 0) : l(l), r(r), v(v) {}

		bool operator < (const Node& lhs) const {
			return l < lhs.l;
		}
	};
	std::set<Node> s;

	std::set<Node>::iterator split(int pos) { // 分裂区间
		std::set<Node>::iterator it = s.lower_bound(Node(pos));
		if (it -> l == pos && it != s.end()) 
			return it;
		-- it;
		if (it -> r < pos) 
			return s.end();

		int64_t 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(int l, int r, int64_t x) {    //区间推平
		std::set<Node>::iterator itr = split(r + 1), itl = split(l);
		s.erase(itl, itr);
		s.insert(Node(l, r, x));
	}

	void add(int64_t l, int64_t r, int64_t x) {    //区间加法
		std::set<Node>::iterator itr = split(r + 1), itl = split(l);
		for (auto it = itl; it != itr; it ++ ) 
			it -> v += x;
	}

	struct Rank {
		int64_t val, cnt;
		bool operator < (const Rank& lhs) const {
			return val < lhs.val;
		}

		Rank(int64_t val, int64_t cnt) : val(val), cnt(cnt) {}
	};

	int rank(int64_t l, int64_t r, int64_t x) {    //查询区间排名为x的数是多少
		std::set<Node>::iterator itr = split(r + 1), itl = split(l);
		std::vector<Rank> vec;
		for (auto it = itl; it != itr; ++ it )
			vec.push_back(Rank(it -> v, it -> r - it -> l + 1));
		std::sort(all(vec));

		int64_t ans = -1;
		for (auto p : vec) { 
			if (p.cnt < x) 
				x -= p.cnt;
			else {
				ans = p.val;
				break;
			}
		}
		return ans;
	}
	
	int64_t query(int64_t l, int64_t r) { // 区间和
		auto itr = split(r + 1), itl = split(l);
		int64_t ans = 0;
		for (auto it = itl; it != itr; ++ it) 
			ans += (it -> v) * (it -> r - it -> l + 1); 
		return ans;
	}
};

标签:Node,ll,pos,朵莉树,int64,split,区间
来源: https://www.cnblogs.com/Haven-/p/16553875.html