?!块块!?

· · 题解

为什么全是点分树?太无聊了。

点分树模板怎么能用点分树做呢?

而且点分树太菜了,稍微复杂一点的邻域问题比如邻域最大独立集就做不了了(也可能是我太菜了)。

哪有没有通用的方法呢?有的,树分块!具体来说,是 top cluster 树分块

具体来说,它就是在树上选 O(B) 个点,满足有一下性质:

  1. 定义虚树上的点为界点,记两个相邻的界点中浅的那个为上界点,深的为下界点,上节点与下节点间的路劲为簇路径。那么在上界点的子树内,下界点子树外的树上的节点数量与簇路径为 O(\frac{n}{B})

  2. 任意一个非界点唯一对应了一对上界点和下界点。

怎么构建呢?先强令根节点为一个界点,然后记录当前未归类的点,如果 \ge B 就新建块,具体可以看代码。

:::success[构建]


struct top_cl
{
    int near,up,down;
  //分别为距离最近的簇路劲上的点,上界点,下界点
}cl[N];
vector<int>bn,asup[N];//bn 存界点,asup 为一个点作为上界点的块的下界点集合。
vector<int>all[N];//在下节点处存储块内节点(不包括界点)
int ls[N],len;//临时储存要加如的点
bool vis[N];//标记一个点是否为界点
inline void link(int x,int y)//以 x 为界点,y 为下界点加块
{
    if(!y) y=ls[len],bn.push_back(y);//y=0 代表一个子树为一块,那么随便选即可。
    asup[x].push_back(y);//加入
    cl[x].near=x;
    for(int v=y;v!=x;v=fa[v]) cl[v].near=v;//簇路劲上直接赋值。
    for(int i=1;i<=len;i++)
    {
        int v=ls[i];
        cl[v].up=x,cl[v].down=y;
        int j=v;
        for(;!cl[j].near;j=fa[j]);//遍历找最近的簇路径
        cl[v].near=cl[j].near;
        if(v!=y)all[y].push_back(v);//加入
    }
    len=0;
}
int st[N],top,lsttop[N];//st 子树内的点  lsttop 配合 st 便利子树
int lstbn[N],wait[N];//lstbn 子树内上一个界点  wait 当前还没有加入的点的数量
inline void build(int x)
{
    lsttop[x]=top;//记录先前
    wait[x]=1;
    int tot=0;
    for(int v:edge[x])
    {
        if(v==fa[x])continue;
        fa[v]=x;
        st[++top]=v;
        build(v);
        wait[x]+=wait[v];
        if(lstbn[v])//存在界点
        {
            lstbn[x]=lstbn[v];
            tot++;
        }
    }
    if(wait[x]>B||tot>1||!fa[x])//要加入的 >B 或有多个界点,此时它一定要是界点或为根
    {
        wait[x]=0;
        lstbn[x]=x;
        if(x!=1)bn.eb(x);//插入界点
        vis[x]=1;
        int j=lsttop[x]+1,now_down=0;
        tot=0;edge[x].eb(-1);
        for(int v:edge[x])
        {
            if(v==fa[x])continue;
            if(v==-1||tot+wait[v]>B||(now_down&&lstbn[v]))//如果便利完了或 >B 了或有要用的下界点了就要加块了
            {
                for(;(v==-1||j<lsttop[v])&&j<=top;j++)ls[++len]=st[j];//将要归类的点压入
                link(x,now_down),tot=now_down=0;
            }
            tot+=wait[v];
            if(lstbn[v])now_down=lstbn[v];
        }
        edge[x].pop_back(); 
        top=lsttop[x];
    }
}

:::

构建完后我们直接在每个块的上下界点处记录 dis_{x,d} 表示距离这个界点距离 \le d 的点的 a 的和,修改就重构块即可,注意为了方便不要包括界点。

考虑查询,块内直接插,然后就枚举其他块,对于当前块判断哪个界点距查询点近,那么就是查询点到块内点的必经点。

最后查询界点即可。

如果查询点就是界点也是一样的。

时间复杂度用 O(1) 的 LCA 就是 O(n \sqrt n)

:::success[code]

#include<bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N=1e5+7;
vector<int>edge[N];
namespace trLCA
{
    #define B 32
    int n0,a[N],s[N],t[N],b[N/B],f[20][N/B];
    inline int Q(int l,int r)
    {
        if(l==r)return b[l];
        int k=__lg(l^r);
        return min(f[k][l],f[k][r]);
    }
    inline int query(int l,int r)
    {
        int x=l>>5,y=r>>5;
        if(x==y)
        {
            int res=1e9;
            for(int i=l;i<=r;i++)res=min(res,a[i]);
            return res;
        }
        if(y-x==1)return min(t[l],s[r]);
        return min({t[l],s[r],Q(x+1,y-1)});
    }
    inline void build(int n)
    {
        for(int l=0;l<n;l+=B)
        {
            int r=min(l+B,n)-1;
            s[l]=a[l],t[r]=a[r];
            for(int i=l+1;i<=r;i++)s[i]=min(s[i-1],a[i]);
            for(int i=r-1;i>=l&&~i;i--)t[i]=min(t[i+1],a[i]);
        }
        n0=n>>5;
        for(int i=0;i<n0;i++)f[0][i]=b[i]=t[i*B];
        for(int i=1,j=2;j<n0;i++,j<<=1)
        {
            for(int l=0;l<n0;l+=j<<1)
            {
                int m=min(l+j,n0),r=min(l+(j<<1),n0);
                f[i][m]=b[m],f[i][m-1]=b[m-1];
                for(int k=m;k<r-1;k++)f[i][k+1]=min(f[i][k],b[k+1]);
                for(int k=m-1;k>l;k--)f[i][k-1]=min(f[i][k],b[k-1]);
            }
        }
    }
    #undef B
    int dfn[N],rk[N],ip,dep[N];
    inline void dfs(int x,int fa)
    {
        a[ip]=dfn[fa];
        dfn[x]=++ip;
        rk[ip]=x;
        for(int v:edge[x])
        {
            if(v==fa) continue;
            dep[v]=dep[x]+1;
            dfs(v,x);
        }
    }
    inline void init(int n,int rt=1)
    {
        dep[rt]=0;
        dfs(rt,0);
        build(n);
    }
    inline int LCA(int x,int y)
    {
        if(x==y)return x;
        if((x=dfn[x])>(y=dfn[y])) swap(x,y);
        return rk[query(x,y-1)];
    }
    inline int getdis(int x,int y,int l=-1)
    {
        if(l==-1)l=LCA(x,y);
        return dep[x]+dep[y]-2*dep[l];
    }
}
using trLCA::LCA;
using trLCA::getdis;
const int B=700;
int n,m;
int fa[N],a[N];
struct top_cl
{
    int fa,near,up,down;
}cl[N];
vector<int>bn,asup[N];
vector<tuple<int,int,int>>all[N];
int ls[N],len;
bool vis[N];
inline void link(int x,int y)
{
    if(!y) y=ls[len],bn.push_back(y);
    asup[x].push_back(y);
    cl[y].fa=x;
    cl[x].near=x;
    for(int v=y;v!=x;v=fa[v]) cl[v].near=v;
    for(int i=1;i<=len;i++)
    {
        int v=ls[i];
        cl[v].up=x,cl[v].down=y;
        int j=v;
        for(;!cl[j].near;j=fa[j]);
        cl[v].near=cl[j].near;
        if(v!=y)all[y].push_back({v,getdis(v,y,cl[v].near),getdis(v,x,x)});
    }
    len=0;
}
int st[N],top,lsttop[N];
int lstbn[N],wait[N];
inline void build(int x)
{
    lsttop[x]=top;
    wait[x]=1;
    int tot=0;
    for(int v:edge[x])
    {
        if(v==fa[x])continue;
        fa[v]=x;
        st[++top]=v;
        build(v);
        wait[x]+=wait[v];
        if(lstbn[v])
        {
            lstbn[x]=lstbn[v];
            tot++;
        }
    }
    if(wait[x]>B||tot>1||!fa[x])
    {
        wait[x]=0;
        lstbn[x]=x;
        if(x!=1)bn.eb(x);
        vis[x]=1;
        int j=lsttop[x]+1,now_down=0;
        tot=0;edge[x].eb(-1);
        for(int v:edge[x])
        {
            if(v==fa[x])continue;
            if(v==-1||tot+wait[v]>B||(now_down&&lstbn[v]))
            {
                for(;(v==-1||j<lsttop[v])&&j<=top;j++)ls[++len]=st[j];
                link(x,now_down),tot=now_down=0;
            }
            tot+=wait[v];
            if(lstbn[v])now_down=lstbn[v];
        }
        edge[x].pop_back(); 
        top=lsttop[x];
    }
}
vector<int>dis[2][N];
int pos[N];
inline void update(int x)
{
    int L=cl[x].down;
    int id=pos[L],lst=0;
    if(!dis[0][id].size())
    {
        int Max=0;
        for(auto [v,d1,d2]:all[L])
            Max=max({Max,d1,d2});
        dis[0][id].resize(Max+1);
        dis[1][id].resize(Max+1);
    }
    else
    {
        for(int&v:dis[0][id])v=0;
        for(int&v:dis[1][id])v=0;
    }
    for(auto [v,d1,d2]:all[L])
    {
        dis[0][id][d1]+=a[v];
        dis[1][id][d2]+=a[v];
    }
    lst=0;for(int&v:dis[0][id])v+=lst,lst=v;
    lst=0;for(int&v:dis[1][id])v+=lst,lst=v;
}
bool vis1[N],vis2[N];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    for(int i=1;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        edge[x].eb(y);
        edge[y].eb(x);
    }
    trLCA::init(n); 
    build(1);
    for(int i=0;i<(int)bn.size();i++)
        pos[bn[i]]=i+1;
    vis1[1]=1;
    for(int v:bn)
        vis1[v]=1,update(v);
    int lstans=0;
    while(m--)
    {
        int opt,x,d;
        cin>>opt>>x>>d;
        x^=lstans,d^=lstans;
        if(opt==0)
        {
            int ans=0;
            if(vis1[x])
            {
                if(x!=1)ans=dis[0][pos[x]][min(d,(int)dis[0][pos[x]].size()-1)];
                vis2[x]=1;
                for(int v:asup[x])
                {
                    vis2[v]=1;
                    int id=pos[v];
                    ans+=dis[1][id][min(d,(int)dis[1][id].size()-1)];
                }
                for(int v:bn)
                {
                    int d1=getdis(v,x);
                    if(d1<=d)
                        ans+=a[v];
                    if(vis2[v])continue;
                    int kl=v;
                    int kr=cl[v].up;
                    int d2=getdis(kr,x);
                    int id=pos[kl];
                    int lim=0,op=0;
                    if(d1<d2)lim=d-d1,op=0;
                    else lim=d-d2,op=1;
                    if(lim>=0)ans+=dis[op][id][min(lim,(int)dis[op][id].size()-1)];
                }
                if(getdis(x,1,1)<=d)ans+=a[1];
                cout<<ans<<'\n';
                lstans=ans;
                vis2[x]=0;
                for(int v:asup[x])
                    vis2[v]=0;
                continue;
            }
            int L=cl[x].down;
            for(auto [v,aa,bb]:all[L])
                if(getdis(x,v)<=d)
                    ans+=a[v];
            for(int v:bn)
            {
                int d1=getdis(v,x);
                if(d1<=d)
                    ans+=a[v];
                if(v==L)continue;
                int kl=v;
                int kr=cl[v].up;
                int d2=getdis(kr,x);
                int id=pos[kl];
                int lim=0,op=0;
                if(d1<d2)lim=d-d1,op=0;
                else lim=d-d2,op=1;
//              cout<<lim<<'\n';
//              if(lim>0)cout<<"-->"<<op<<' '<<dis[op][id][min(lim,(int)dis[op][id].size()-1)]<<'\n';
                if(lim>=0)ans+=dis[op][id][min(lim,(int)dis[op][id].size()-1)];
            }
            if(getdis(x,1,1)<=d)ans+=a[1];
            cout<<ans<<'\n';
            lstans=ans;
        }
        else
        {
            a[x]=d;
            if(vis1[x]||x==1)continue;
            update(x);
        }
    }
}

:::

效率大概是点分树的一半.

优势

首先最大的优势就是支持任意可合并信息。

然后就是空间线性!但在本题没有体现出优势。

更多?

本题似乎可以做到单 \log,具体可以去拜读 LHF 在 2025 年的集训队论文《浅谈树上邻域问题的一种基于长链剖分的解法》。