静态猫树 学习笔记
前言
- msjing被整体二分创飞了所以来写猫树了
静态猫树
- 猫树是线段树的一种,
不是猫上树,不支持修改,建树大概是 \(O(n\) \(\log\) \(n)\) 的 - 那这个玩意好废物我们为什么要学捏
- 因为它支持 \(O(1)\) 的查询
让你学一定有学的道理- 这个东西其实是处理线性基的,但是msjing不会
- 我们现在介绍静态猫树,还有一种猫树分治,等会说
操作
- 静态猫树如何实现 \(O(1)\) 查询呢
- 其实就是把查询用的 \(\log\) 放到建树里
- 共两个函数,还挺少的对吧
- 首先,我们考虑一棵完全二叉树形状的线段树
- 对于两个节点,其 \(lca\) 是它们第一次分开的地方,我们观察发现,woc!这个 \(lca\) 的编号就是两个节点的编号二进制最长连续相同区间(就是前面那一长串)
- 图不给了,\(Linux\) 不太好画
- 我们利用这个性质,就可以快速找出两个节点的 \(lca\) 了
- 我们为什么要找这个捏
- 因为有一些特别 NB 的性质,我直接偷 oiwiki 的过来吧,不想写了
粘的
- 在查询 \([l,r]\) 这段区间的信息和的时候,将线段树树上代表 \([l,l]\) 的节点和代表 \([r,r]\) 这段区间的节点在线段树上的 \(LCA\) 求出来,设这个节点 \(p\) 代表的区间为 \([𝐿,𝑅]\),我们会发现一些非常有趣的性质:
-
\([𝐿,𝑅]\)这个区间一定包含 \([l,r]\)。显然,因为它既是 \(l\) 的祖先又是 \(r\) 的祖先。
-
\([l,r]\) 这个区间一定跨越 \([L,R]\) 的中点。由于 \(p\) 是 \(l\) 和 \(r\) 的 \(LCA\),这意味着 \(p\) 的左儿子是 \(l\) 的祖先而不是 \(r\) 的祖先,\(p\) 的右儿子是 \(r\) 的祖先而不是 \(l\) 的祖先.因此,\(l\) 一定在 \([𝐿,mid]\) \([L,mid]\) 这个区间内,\(r\) 一定在 \((mid,R]\) 这个区间内。
-
- 那预处理如何做呢?
- 我们搞一个数组,存 \((l,mid]\) 的后缀和 \([mid,r)\) 的前缀,由于我们上面说的性质必须是完全二叉树才有,所以我们建完全二叉树,利用二进制,我们可以求出两个叶子节点的 \(lca\),我们只需要在建树时再多处理一个数组就行了
- 那么猫树到底怎么搞,我们看道例题
例题P11265 【模板】静态区间半群查询
- 题给了你一个暴力,观察发现查询太man了,所以我们可以用猫树优化查询
- 我们按照上面说的预处理,建树,然后查询区间的矩阵积
- 好像没什么,主要是看代码和实现
- 这题并不用管什么 \((min,+)\) 矩阵积,暴力都给你了,直接用就行
- 有许多细节:
- 注意矩阵乘的前后顺序,矩阵乘不满足交换律
- 如果你预处理 \(\log\) 数组,会由于内存的限制而处理不够,所以只能换 \(C++\) 库里的
- 然后就没啥了
点击查看代码
#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 mul(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 = 1e6 + 9;
int n, m, ans;
mat a[N];
int lg[N],p[N];
namespace Cat
{
mat t[30][N];
void Build(int rt,int l,int r,int d)
{
if (l == r)
{
if (l<=n) t[d][l]=a[l];
p[l]=rt;
return;
}
int mid=(l+r) >> 1;
int ls=min(mid,n),rs=min(r,n);
if (mid<=n) t[d][mid]=a[mid];
if (mid+1<=n) t[d][mid+1]=a[mid+1];
for (int i=ls-1;i>=l;i--) t[d][i]=mul(a[i],t[d][i+1]);
for (int i=mid+2;i<=rs;i++) t[d][i]=mul(t[d][i-1],a[i]);
Build(rt<<1,l,mid,d+1),Build(rt<<1|1,mid+1,r,d+1);
}
mat que(int l,int r)
{
if (l == r) return a[l];
int d=log2(p[l]>>((int)log2(p[l]^p[r])+1));
return mul(t[d][l],t[d][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]);
Cat::Build(1,1,1<<20,0);
for (int l, r; m; --m) {
rnd.genqry(l, r, n);
// mat ret=Cat::que(l,r);
// for (int i=0;i<2;i++)
// for (int j=0;j<2;j++)
// cerr << ret.a[i][j] << " ";
// cerr << endl;
out.setres(Cat::que(l,r));
}
return cout << out.ans << endl, 0;
}
猫树分治
- 猫树分治就是在猫树上做分治,这要结合例题看
例题P6240 好吃的题目
- 这是背包 \(DP\),瞎搞一下转移方程
- 但是转移是接近 \(^2\) 的,会炸
- 我们注意到要求区间,不要求在线,考虑什么离线算法
莫队分治- 我们还知道背包是可以合并的,这个很重要,那么我们就可以直接在猫树上做分治,反正都能合并
- e,好像没啥了,分治可以类比整体二分,请注意:学整体二分一定要学 vecter 做法,别管什么空间的,
反正都能卡过去,真的,msjing数组写了不对换 vector 一发过了
点击查看代码
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
constexpr int maxn=4e4+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,tt;
int h[maxn],w[maxn];
struct _ {int l,r,t,id;};
vector<_> q;
int f[maxn][210];
int ans[(int)2e5+10];
void sol(int l,int r,vector<_> q)
{
if (q.empty()) return;
if (l>r) return;
int mid=(l+r) >> 1;
for (int i=0;i<=200;i++) i<h[mid] ? f[mid][i]=0 : f[mid][i]=w[mid];
for (int i=mid-1;i>=l;i--)
{
for (int j=h[i];j<=200;j++) f[i][j]=max(f[i+1][j],f[i+1][j-h[i]]+w[i]);
for (int j=0;j<h[i];j++) f[i][j]=f[i+1][j];
}
for (int i=0;i<=200;i++) i<h[mid+1] ? f[mid+1][i]=0 : f[mid+1][i]=w[mid+1];
for (int i=mid+2;i<=r;i++)
{
for (int j=h[i];j<=200;j++) f[i][j]=max(f[i-1][j],f[i-1][j-h[i]]+w[i]);
for (int j=0;j<h[i];j++) f[i][j]=f[i-1][j];
}
// cerr << "1111111" << endl;
vector<_> ls,rs;
while (!q.empty())
{
auto bk=q.back();
if (bk.r<=mid) ls.pb(bk);
else if (bk.l>mid) rs.pb(bk);
else
for (int j=0;j<=bk.t;j++)
ans[bk.id]=max(ans[bk.id],f[bk.l][j]+f[bk.r][bk.t-j]);
q.pop_back();
}
sol(l,mid,ls),sol(mid+1,r,rs);
}
int main()
{
// freopen("P6240_1.in","r",stdin);freopen("777.out","w",stdout);
n=read(),m=read();
for (int i=1;i<=n;i++) h[i]=read();
for (int i=1;i<=n;i++) w[i]=read();
for (int i=1;i<=m;i++)
{
int l=read(),r=read(),t=read();
if (l == r) ans[i]=h[l]<=t?w[l]:0;
else q.pb({l,r,t,i});
}
// cerr << "----------" << endl;
sol(1,n,q);
// cerr << "-----------" << endl;
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
后话
- 因为没时间搞猫树了,所以草率的说一说,e,没了
- 没有小剧场哦~

浙公网安备 33010602011771号