数据结构自学日志——猫树

谈一种神奇的数据结构——猫树。

猫树的作用与ST表几乎一致,但是它能维护的东西比ST表更为广泛。

引入

先介绍一下猫树的创始人——immortalCO,一位巨犇。

当我们遇到这样一道题——要以 \(O(1)\) 的复杂度求一段区间的 \(gcd\) ,我们这时就不能用线段树或ST表了,就可以使用猫树。

基本思想

猫树的基本思想为,将询问的区间分为两个已经预处理好的,可以 \(O(1)\) 查询的区间。

代码实现

就像线段树一样,猫树的会把一段区间分成左右两段,从中点处分别向两边遍历,(注意:左半边的遍历是倒序的),然后记录每一次的结果,复杂度: \(O(nlogn)\)

用求 \(gcd\) 的一段代码来演示这个操作 \(\downarrow\)

build(num << 1, l, mid, dep + 1); //build是建树函数,用递归实现。
build(num << 1 | 1, mid + 1, r, dep + 1); //num是此时的编号,dep是此时的深度
dp[mid][dep] = a[mid]; //初始化左半边的起点
for (int i = mid - 1; i >= l; --i) { //注意:左半边的遍历是倒序的
  dp[i][dep] = a[i];	
  dp[i][dep] = std::__gcd(dp[i][dep], dp[i + 1][dep]); //与下一位比较	
}
dp[mid + 1][dep] = a[mid + 1]; //初始化右半边的起点
for (int i = mid + 2; i <= r; ++i) {
  dp[i][dep] = a[i];
  dp[i][dep] = std::__gcd(dp[i][dep], dp[i - 1][dep]);
}

查询操作

在查询时,我们会找到一个能把要查询的区间分成两部分的 \(mid\) 然后 实现 \(O(1)\) 查询。

那么,如何找到那个 \(mid\) 呢?

我们发现,每一个深度都有若干个 \(mid\) 而且,只要我们找到了正确的 \(mid\) 所在的深度,我们就可以直接用 \(gcd(dp[l][dep], dp[r][dep])\) 就可以查询了。

那么,如何找到那个深度呢。

我们发现,对于任意的两个 \(l, r\) ,能把他们分割成两块的那个点所在的区间一定是 \(l, r\) 在树上的 \(LCA\) ,如何求呢。

因为,猫树每次一段区间分成两块,所以,树上两点的 \(LCA\) ,即为,他们编号的最长公共前缀。

So,我们就可以记录每个数字在二进制下的长度,并记录每一个下标所对应的树上编号,这样,就可以做到 \(O(1)\) 查询了。

code:

#pragma GCC optimize(3, "Ofast", "inline")
#include <iostream>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <climits>
#include <random>
#include <bitset>
#include <unordered_map>
#define orz %
#define ll long long
#define juruo Optimist_Skm
#define mid ((l + r) >> 1)
#define pii std::pair<int, int>
#define fi first
#define se second
#define eb emplace_back
#define pb push_back
#define m_p std::make_pair
#define pq std::priority_queue<int>
#define pq_min std::priority_queue<int, std::vector<int>, std::greater<int> >
#define open(x) freopen(#x".in", "r", stdin);freopen(#x".out", "w", stdout);
#define test(x) cout << "Test: " << x << '\n'
#define close fclose(stdin);fclose(stdout);
#define ull unsigned long long
#define debug(); printf("qwq\n");

namespace Fast_Skm {

	template <typename T>
	inline void read(T &x) {
		T s = 0, w = 1;
 		char c = getchar();
		while(!isdigit(c)) {
			if(c == '-')  w  = -1;
			c = getchar();
		}
		while(isdigit(c))
			s = (s << 1) + (s << 3) + (c & 0xcf), c = getchar();
			
		x = s * w;
		return ;
	}

	template <typename T, typename... Arp>
	inline void read(T &x, Arp &...arp) {
		read(x), read(arp...);
        return ;
	}

	template <typename T>
	inline void write(T x) {
		if(x < 0) x = -x, putchar('-');
		int p = 0;
		static char s[100];
		do {
			s[++p] = x orz 10 + '0';
			x /= 10;
		} while (x);
		while(p) putchar(s[p--]);
		putchar('\n');
	}

	template <typename T, typename... Arp>
	inline void write(T &x, Arp &...arp) {
		write(x), write(arp...);
		return ;
	}

	template <typename S, typename T>
	inline void smax(S &x, T y) {
		x = (x > y) ? x : y;
	}

	template <typename S, typename T>
	inline void smin(S &x, T y) {
		x = (x < y) ? x : y;
	}

	inline void quit() {
		exit(0);
		return ;
	}
	
	inline ll quick_pow(ll a, ll b, ll P) {
		ll ans = 1;
		while(b >= 1) {
			if(b & 1) {
				ans = ans * a % P;
			}
			a = a * a % P;
			b >>= 1;
		}
		return ans;
	}
	
	template <typename T>
	inline T exgcd(T a, T b, T &x, T &y) {
		if(b == 0) {
		 	x = 1; y = 0;
		 	return a;
		}
		int gcd = exgcd(b, a % b, x, y);
		int tmp = y;
		y = x - a / b * y;
		x = tmp;
		return gcd;
	}


} using namespace Fast_Skm;

const int N = 5e5 + 5, M = 25;
int a[N], dp[N][M], n, m, lg[N], pos[N];

inline void build(int num, int l, int r, int dep) {
	if (l == r) {
		pos[r] = num;
		return ;
	}
	build(num << 1, l, mid, dep + 1);
	build(num << 1 | 1, mid + 1, r, dep + 1);
	dp[mid][dep] = a[mid];
	for (int i = mid - 1; i >= l; --i) {
		dp[i][dep] = a[i];
		dp[i][dep] = std::__gcd(dp[i][dep], dp[i + 1][dep]);
	}
	dp[mid + 1][dep] = a[mid + 1];
	for (int i = mid + 2; i <= r; ++i) {
		dp[i][dep] = a[i];
		dp[i][dep] = std::__gcd(dp[i][dep], dp[i - 1][dep]);
	}
	return ;
}

signed main() {

	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);

	read(n, m);
	for (int i = 1; i <= n; ++i) {
		read(a[i]);
	}

	int len = 1;
	while(len < n) len <<= 1; //此处的程度并不是n,而是大于n的第一个的2的次幂。

	build(1, 1, len, 1);

	lg[1] = 0; lg[0] = 0;
	for (int i = 1; i <= len << 1; ++i) {
		lg[i] = lg[i >> 1] + 1; //预处理出每一个数在二进制下的长度
	} 

	for (int i = 1; i <= m; ++i) {
		int l, r;
		read(l, r);
		if (l == r) { //此处必须特判
			write(a[l]);
			continue;
		}
		int p = lg[pos[r]] - (lg[pos[r] ^ pos[l]]); //计算 LCA 的深度
		write(std::__gcd(dp[l][p], dp[r][p]));
	}
	
	return 0;
}

推荐例题

洛谷P3685

posted @ 2024-07-22 10:20  Optimist_Skm  阅读(227)  评论(0)    收藏  举报