静态猫树 学习笔记

前言

  • 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,没了
  • 没有小剧场哦~
posted @ 2026-07-23 21:02  msjing  阅读(8)  评论(1)    收藏  举报