其他分享
首页 > 其他分享> > 好玩的数据结构

好玩的数据结构

作者:互联网

猫树

基本信息

这是一个比较良心的数据结构,代码属于比较易懂也好打。只是有可能目前的应用范围有点小。

猫树呢,大概是一个能 \(O(n\log n)\) 建树, \(O(1)\) 查询,不太能修改(你非要的话就 \(O(n)\) ),维护信息需要满足结合律且能在可接受复杂度合并

算法实现

建树

以查询出发,我们假设我们答案要的区间是 \([{\rm l},\ {\rm r}]\) ,那么假设我们已经能知道 \([{\rm l},\ {\rm mid}]\) 和 \([{\rm mid} + 1,\ {\rm r}]\) 的答案,那是不是直接合并就行了。

我们想象线段树建树是 \([{\rm L},\ {\rm R}]\) 递归到 \([{\rm L},\ {\rm Mid}]\) 和 \([{\rm Mid} + 1,\ {\rm R}]\) ,以左边这个小区间为例,如果我们直接处理 \([{\rm L}-{\rm Mid},\ {\rm Mid}]\) 的答案,因为最多递归 \(\log n\) 次,整个时间复杂度就是 \(O(n \log n)\) ,还是比较能够接受的。

然后我们发现假如一个答案区间 \([{\rm l}, {\rm r}]\) 的 \({\rm l}\) 和 \({\rm r}\) 在递归的同一小区间,就并不是我们处理时能取到的答案,直到最后递归到了一个区间 \({\rm l}\) 和 \({\rm r}\) 被分开了,我们就能找到一个 \({\rm mid}\) 能合并两边的答案,从而我们就达到了目的。

然后我们思考怎么存答案,对于每一层,不同的区间所在范围互不交叉,所以我们以层数为第一维,具体的哪一个位置为第二维。

查询

但是没完,我们要求的是 \(O(1)\) 查询,但是我们好像不知道怎么一下子定位在哪个区间能合并。

能发现两个点作为两个叶子节点,他们的 \({\rm lca}\) 就是答案所在的区间,因为此时两个点所在的区间被合并成一个大区间了,正好就是有一个 \({\rm mid}\) 大于 \({\rm l}\) 且小于 \({\rm r}\) 。

那是不是什么 ST 表就可以了??其实还可以更简单!!

好像一些性质没用上,我们来想假如两个值在一个区间被分开了,那他们会有什么联系。

好像没有什么性质吧,因为边界是 \([1,\ n]\) ,那就改成 \([1,\ 2 ^ m]\) ,其中是 \(2 ^ m\) 大于 \(n\) 中最小的数。本质上时间复杂度不会变,但是加入两个数被分开,假如目前是第 \(k\) 层,那就是第 \(k\) 高位不一样。

这就相当于两个异或之后前 \(k - 1\) 位全部消失,那我们只要预处理二进制位数,我们就能很快得到 \(k\) 的值,因为答案存的方式正好需要 \(k\) ,正好就可以马上算出答案了,也就是真正意义上的 \(O(1)\) 了。

修改

修改你大爷,以单点修改为例,影响的地方从第一层一直到自己所在的叶子,所在区间大小和为:

\[\sum_{i = 1} ^ {m} \frac{n}{2 ^ i} = n \]

搞屁,修改都是 \(O(n)\) 的,那就不修改(当然什么修改次数很小倒是可以考虑考虑)

猫树分治

前景提要:某些卡空间但是没有要求强制在线的题

其实处理方法很简单,就有点归并排序的意思,每次递归两个小区间,就扫一遍询问,把答案能算的算了,不能算了分成两拨继续递归就好啦。

这样的话时间复杂度不变,空间复杂度从原先 \(O(n \log n)\) 变成 \(O(n)\) 了。

代码

以例题中的 GSS1 为例。

/*

*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4e5 + 10;
int n, a[N], len = 1, lg[N], m;
inline int read() {
	char ch = getchar();
	int s = 0, w = 1;
	while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();}
	while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar();}
	return s * w;
}
struct immortalCO {
	#define lson (now << 1), l, mid
	#define rson (now << 1 | 1), mid + 1, r
	int pos[N], p[21][N], s[21][N];
	inline void build(int now, int l, int r, int d) {
		if (l == r) return pos[l] = now, void();
		int mid = (l + r) >> 1, all, mx;
		p[d][mid] = s[d][mid] = all = mx = a[mid];
		mx = max(mx, 0);
		for (int i = mid - 1; i >= l; --i) {
			all += a[i]; mx += a[i];
			p[d][i] = max(p[d][i + 1], mx);
			s[d][i] = max(s[d][i + 1], all);
			mx = max(mx, 0);
		}
		p[d][mid + 1] = s[d][mid + 1] = all = mx = a[mid + 1];
		mx = max(mx, 0);
		for (int i = mid + 2; i <= r; ++i) {
			all += a[i]; mx += a[i];
			p[d][i] = max(p[d][i - 1], mx);
			s[d][i] = max(s[d][i - 1], all);
			mx = max(mx, 0);
		}
		build(lson, d + 1); build(rson, d + 1);
	}
	inline int query(int l, int r) {
		if (l == r) return a[l];
		int d = lg[pos[l]] - lg[pos[l] ^ pos[r]];
		return max(max(p[d][l], p[d][r]), s[d][l] + s[d][r]);
	}
	#undef lson
	#undef rson
} ct;
int main() {
	n = read();
	for (int i = 1; i <= n; ++i) a[i] = read();
	for (; len < n; len <<= 1);
	for (int i = 2; i <= (len << 2); ++i) lg[i] = lg[i >> 1] + 1;
	ct.build(1, 1, len, 1);
	m = read();
	while (m--) printf("%d\n", ct.query(read(), read()));
	return 0;
}

例题

GSS1(最大子段和)

GSS5(区间端点带限制最大子段和)

好吃的题目(猫树分治)

[CmdOI2019]口头禅(猫树分治 + SAM )

鸣谢:

Judge_Cheung的博客

immortalCO喵的博客

标签:int,mid,答案,区间,rm,数据结构,好玩,mx
来源: https://www.cnblogs.com/Illusory-dimes/p/15916803.html