猫树
介绍
众所粥汁,线段树是一个非常牛逼的数据结构,只要维护的区间支持区间可加性,它都可以轻松维护,它以 \(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;
}

浙公网安备 33010602011771号