闲话 22.9.25[6]

闲话

调全体T4
六点改出来了,但大样例不认为我改出来了
于是又花了两个小时修改不存在的错误

最近在做这题
[JRKSJ R4] risrqnis
想做主要是因为上琴糖
但是做着做着发现很神 而且我不会
请大家都来切一下(

所以写了珂朵莉树。
主要是因为复杂度的证明
这题一样神奇

命は綺麗なわけじゃない

美しい人生なんてない

呼吸が上手く出来ないのは

生きてる証拠だ

关于颜色段均摊

又名珂朵莉树。

总而言之是一种通过均摊证出来的正确算法。特用于维护类似于区间改成一个值(或可以快速确定值)的操作。
具体有以下两种:

  1. 四种及以下操作,其中一个是 \([l,r]\) 赋值为 \(v\)。保证数据完全随机。
  2. 操作是 \([l,r]\) 赋值为 \(v\)

先说1的做法。我们使用一个 set,其中每个元素记录 \(v\) 相同的区间 \([l, r]\)。set 的小于就按 \(l\) 小于重载,毕竟你得保证这里面的区间并是 \([1,n]\) 并且没有交。

#define IT set<node>::iterator
struct node {
	mutable int l, r, val;
	node(int lf, int rt, int v) : l(lf), r(rt), val(v) {}
	bool operator < (const node & b) const 
	{ return l < b.l; }
};
set<node> s;

就这么写一下。set 的元素操作一般带一个 const 修饰,为了进行接下来的操作我们需要给元素上一个 mutable 保证能改值。记得把迭代器定义成好写的名字,因为一会儿得用很多次。

珂朵莉树的核心操作是分裂区间,即 \(split(pos)\) 函数。它会把 \(pos\) 所在区间 \([l,r]\) 分裂成 \([l,pos-1]\)\([pos,r]\) 然后返回后一段。直接在 set 里查一下再处理就行。

inline IT split(int pos) {
	IT it = s.lower_bound(node(pos, 0, 0));
	if (it != s.end() and it->l == pos) return it;
	it--; if (it->r < pos) return s.end();
	int l = it->l, r = it->r, val = it->val;
	s.erase(it);
	s.insert(node(l,pos-1,val));
	return s.insert(node(pos,r,val)).first; // insert返回一个 <iterator,bool> 的 pair
}

然后有了分裂就得有合并,即区间赋值函数 \(assign(l,r,v)\)@brief This does what you think it does.

inline void assign_val(int l, int r, ll val) {
	IT itr = split(r+1);
	IT itl = split(l);
	s.erase(itl, itr);
	s.insert(node(l,r,val));
}

记得先 split 右边再 split 左边。因为左边 split 出来的迭代器可能会被右边 erase 掉,它可能指向一个非法地址。

然后有了区间赋值操作,剩下的东西就直接暴力就好。拿板子题CF896C说。show you the code:
区间加:

inline void add(int l, int r, ll val) {
	IT itr = split(r+1);
	IT itl = split(l);
	while (itl != itr) itl->val += val, itl++;
}

区间 \(k\) 小:

struct rnks {
	int num, cnt;
	bool operator < (const rnks & b) const
		{ return num < b.num; }
	rnks(int nm, int ct) : num(nm), cnt(ct) {}
};

inline ll rnk(int l, int r, ll k) {
	vector <rnks> vp;
	IT itr = split(r+1);
	IT itl = split(l);
	vp.clear();
	while(itl != itr) {
		vp.push_back(rnks(itl->val, itl->r - itl->l + 1));
		itl++;
	} sort(vp.begin(), vp.end()); 
	int pos;
	for (pos = 0; pos < vp.size(); ++pos) {
		if (k > vp[pos].cnt) k -= vp[pos].cnt;
		else break;
	} return vp[pos].num;
}

区间 \(p\) 次方和:

inline ll sum(int l, int r, int pw, int p) {
	IT itr = split(r+1);
	IT itl = split(l);
	ll ret = 0;
	while (itl != itr) {
		ret = (ret + (itl->r - itl->l + 1) * qp(itl->val, pw, p)) % p, itl++;
	} return ret;
}

然后证一下复杂度:

引理:在长度为1的区间内随机撒两个点,它们的间距为 \(\frac 13\)
证:考虑积分。我们发现这个玩意的答案是

\[\iint_D |y - x| \text dx\text dy \]

其中 \(D\)\(x = 0, x = 1, y = 0, y = 1\) 围成的范围。然后进行一些初等的积分。

\[\begin{aligned} &\iint_D |y - x| \text dx\text dy \\ = &\ \int_0^1 dy \int_0^1 |y - x| \text dx \\ = &\ 2\int_0^1 \text dy \int_0^y (y - x) \text dx \\ = &\ 2\int_0^1 \text dy (\int_0^y y\text dx - \int_0^y x\text dx) \\ = &\ 2\int_0^1 \text dy (y^2 - \frac 12 y^2) \\ = &\ \int_0^1 y^2 \text dy \\ = &\ \frac 13 \end{aligned}\]

因此由于数据随机,区间长度大致是 \(\frac n3\) 的(实际上期望是 \(\frac {n-1} 3\) 但需要一些离散和式的操作 麻烦不写)

定理:当有 \(\frac 14\) 的操作为区间赋值时,珂朵莉树内维护的区间数量的确界为 \(\Theta(\log n)\)
不证,因为不会。

lxl说当有 \(\frac 13\) 的操作为赋值时有上界 \(O(\log\log n)\)
也不会证。

例题:Willem, Chtholly and Seniorious

对于2情况,我们发现每个区间扔进珂朵莉树时只会增加 \(O(1)\) 个区间。每个区间花费 \(O(\log n)\) 的复杂度维护(split + assign),摊完复杂度是 \(O(n \log n)\) 的。因此直接维护就行,这里的复杂度一般不是瓶颈。

例题:[YER 2021] TEST_152

eazy round 真就 eazy 题 希望大家都能去切一下水题(
直接用珂朵莉树维护颜色段就行,每次加入删除连续段时对应地修改贡献,这部分的总复杂度是 \(O(n \log n)\) 的。

posted @ 2022-09-26 06:41  joke3579  阅读(133)  评论(8编辑  收藏  举报