猫树

介绍

众所粥汁,线段树是一个非常牛逼的数据结构,只要维护的区间支持区间可加性,它都可以轻松维护,它以 \(O(n)\) 的建树复杂度和 \(O(log n)\) 的查询复杂度几乎被所有人青睐虽然常数有点大,但这也不免有一些毒瘤题故意卡你线段树做法。

这能忍?于是你从网上开始翻题解,翻到了一个叫「猫树」的数据结构。

所以猫树为什么叫猫树? 猫树之所以叫“猫树”,是因为它最早由程序员猫锟提出,同时名字也带有“可爱”的寓意。

所谓猫树,其实就是一种静态线段树
没错,猫树只支持查询。
猫树可以通过 \(O(nlogn)\) 的建树复杂度,达到恐怖的 \(O(1)\) 的查询复杂度。
这复杂度怎么看着这么像ST表?

原理

设两个叶节点 \([l,l]\)\([r,r]\),以及其最近公共祖先(\(lca\)) 为 \([L,R]\),其中点为 \(mid\)
我们就会发现一些有趣的性质。

  • \([L,R]\) 一定包括 \([l,l]\)\([r,r]\)
  • \([l,r]\) 一定跨过 \([L,R]\)\(mid\)

有了以上性质,我们就可以通过查询 \([l,mid]\)\([mid+1,r]\) 快速得到答案。

实现

当我们到达线段树的一个节点 \([l,r]\) 时,额外处理 \([l,mid]\) 的后缀与 \([mid+1,r]\) 的前缀。
这就是为什么猫树的建树复杂度时 \(O(nlogn)\) 的了。
这样如果我们要查 \([l,r]\),那么只需要找到 \([l,l]\)\([r,r]\)\(lca\),然后将 \(lca\)\([l,mid]\)\([mid+1,r]\) 合并得到答案,即可完成 \(O(1)\) 查询,吗?
\(lca\) 怎么求?
暴力?倍增?那就不是 \(O(1)\) 了。
ST表?代价又太大。

堆式建树

我不知道你们有没有观察普通线段树的节点编号的二进制码。
如果这是一个左子节点,就相当于在其父节点编号后面添一个 \(0\),如果右节点则是添一个 \(1\)
那么我们可以说,每一个节点的编号的二进制码就是从根走到这个节点的路径。
如果我们强制将树建成一颗满二叉树(每个叶节点的二进制码一样长),那么两个节点的 \(lca\) 就是这两个节点编号的 \(lcp\) (最长公共前缀)。
那么就可以得到 lac(x,y)=x>>(log2(x^y)+1)
这样就可以快速求得 \(lca\) 了。

【模板】静态区间半群查询\(^{luoguP11265}\)

卡长毒瘤猫树板题
没有什么要说的,就是用猫树维护这个区间矩阵乘。
过不了的卡卡就过了。
这题log没有初始化纯属没辙了,大家不要跟我学。

#include <bits/stdc++.h>
using namespace std;
struct mat {
	int a[2][2];
	mat(){
		a[0][0] = a[1][1] = 0;
		a[1][0] = a[0][1] = 0x3f3f3f3f;
	}
	mat(int x, int y, int z, int w) {
		a[0][0] = x, a[0][1] = y, a[1][0] = z, a[1][1] = w;
	}
};
mat operator * (const mat& x, const mat& y){
	return {min(x.a[0][0] + y.a[0][0], x.a[0][1] + y.a[1][0]),
			min(x.a[0][0] + y.a[0][1], x.a[0][1] + y.a[1][1]),
			min(x.a[1][0] + y.a[0][0], x.a[1][1] + y.a[1][0]),
			min(x.a[1][0] + y.a[0][1], x.a[1][1] + y.a[1][1])};
}
struct random {
	static uint64_t splitmix64(uint64_t x) {
		x += 0x9e3779b97f4a7c15;
	    x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
	    x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
	    return x ^ (x >> 31);
	}
	uint64_t rnd() {
		sd ^= sd << 13, sd ^= sd >> 7;
	    return sd ^= sd << 17;
	}
	void init() { cin >> sd >> b, sd = splitmix64(sd); }
	void genmat(mat& res) {
		uint64_t val = rnd();
    	for (int i : {0, 1})
    		for (int j : {0, 1}) res.a[i][j] = val >> ((i << 1 | j) << 4) & 0xff;
	}
	void genqry(int& l, int& r, int n) {
	    if ((rnd() & 1) && b) {
	    	int c = rnd() % (n - b);
	    	l = rnd() % (n - c) + 1, r = l + c;
	    }else{
			l = rnd() % n + 1, r = rnd() % n + 1;
			if (l > r) swap(l, r);
		}
	}
	uint64_t sd;
	int b;
} rnd;
struct output {
	int ans, kv[2][2];
	void init() {
	for (int i : {0, 1})
    	for (int j : {0, 1}) cin >> kv[i][j];
	}
	void setres(mat res) {
    	int tmp = 0;
    	for (int i : {0, 1})
		for (int j : {0, 1}) tmp += res.a[i][j] ^ kv[i][j];
    	ans ^= tmp;
	}
} out;
constexpr int N = 12e5 + 9;//神秘大小
int n, m, ans;
mat a[N];
int q[N<<1];
mat t[25][N];
void build(int k,int l,int r,int d){
	if(l==r){
		t[d][l]=a[l],q[l]=k;
		return;
	} 
	int mid=(l+r)>>1;
	int ls=min(mid,n),rs=min(n,r);
	if(l<=n){
		t[d][ls]=a[ls];
		for(int i=ls-1;i>=l;--i){
			t[d][i]=a[i]*t[d][i+1];
		}
	}
	if(mid+1<=n){
		t[d][mid+1]=a[mid+1];
		for(int i=mid+2;i<=rs;++i){
			t[d][i]=t[d][i-1]*a[i];
		}
	}
	build(k<<1,l,mid,d+1),build(k<<1|1,mid+1,r,d+1);
} 
mat query(int l,int r){
	if(l==r) return a[l];
	int k=log2(q[l]>>(int)(log2(q[l]^q[r])+1));
	return t[k][l]*t[k][r];
}

signed main() {
	cin.tie(nullptr)->sync_with_stdio(false);
	cin >> n >> m, rnd.init(), out.init();
	for (int i = 1; i <= n; ++i) rnd.genmat(a[i]);
	int nn=n;
	build(1,1,1<<((int)log2(nn)+1),0);
	for (int l, r; m; --m) {
		rnd.genqry(l, r, n);
		out.setres(query(l,r));
	}
	return cout << out.ans << endl, 0;
}
posted @ 2026-07-23 21:44  LZYXT  阅读(7)  评论(0)    收藏  举报