一种猫树解法

lr580 发表于 2年前 · 关联问题 云烟团队激励(30分)

继 ST 表, zkw 线段树后,发现了一种新的解法——猫树

维护静态线段树,使得普通查询复杂度为 O(1)

对查询的叶子节点 l,r ,求出 LCA ,那么 l,r 一定分别是它的左儿子和右儿子。如果对每个节点维护 mid 为起点的右子前缀和、左子后缀和,那么就可以在 LCA 上 O(1) 计算答案。空间复杂度升为 O(n\log n) (主定理可知)

若线段树是满二叉树,那么二进制最长公共前缀 LCP(x,y)=x>>\log(x\oplus y) ,而 LCA 就是 LCP,预处理 log 即可 O(1) 求 LCA 。

实现:建树可以不建叶子节点(那么查询叶子节点要特判)。建树时先求出 mid 信息,然后暴力向左向右拓展后缀/前缀和(因为深度为 \log n ,所以开 n\times \log n 空间即可)。建树时对每个下标求出线段树叶子节点编号,记作 pos[i] ,那么 LCA 的深度(假设从 1 开始)可以表示为 \log(pos[r])-\log(pos[l]\oplus pos[r]) 。查询时对这个深度的前缀和、后缀和合并即可。

#include <bits/stdc++.h> using namespace std; #define sc(x) scanf("%lld", &x) typedef long long ll; #define mn 131082 // 1<<log(1e5,2)+10 #define ml 20 ll n, m, a[mn], n2, lg[mn * 2], l, r; ll pos[mn], gcd[mn][ml]; #define lfs p << 1 #define rfs p << 1 | 1 #define mkcf ll cf = (lf + rf) >> 1 void build(ll p, ll lf, ll rf, ll dep) { if (lf == rf) { pos[lf] = p; return; } mkcf; build(lfs, lf, cf, dep + 1); build(rfs, cf + 1, rf, dep + 1); gcd[cf][dep] = a[cf]; for (ll i = cf - 1; i >= lf; --i) { gcd[i][dep] = __gcd(gcd[i + 1][dep], a[i]); } for (ll i = cf + 1; i <= rf; ++i) { gcd[i][dep] = __gcd(gcd[i - 1][dep], a[i]); } } signed main() { sc(n), sc(m); for (ll i = 1; i <= n; ++i) { sc(a[i]); } for (n2 = 1; n2 < n; n2 <<= 1) //满二叉下节点有n2个 ; for (ll i = 2; i <= n2 * 2; ++i) { lg[i] = lg[i / 2] + 1; } build(1, 1, n2, 1); while (m--) { sc(l), sc(r); if (l == r) //猫树不存叶子节点 { printf("%lld\n", a[l]); continue; } ll k = lg[pos[r]] - lg[pos[l] ^ pos[r]]; printf("%lld\n", __gcd(gcd[l][k], gcd[r][k])); } return 0; }