点分树

事实上比较朴素。

P6329 【模板】点分树 | 震波

大致思路是将点分治的那个过程建成一棵树。每一层的重心和下一层的中心连边。

这棵树有两个重要性质:树高保证为 \(\log n\),任意两点的 lca 一定在这两个点的路径上。同时,一个有趣的性质是一个点到其子树内所有点的距离都不超过这个点的子树大小。这个可能可以为一些特殊查询(比如说距离恰好为 \(k\))做到 \(O(n\log n)\) 提供了条件。

于是我们可以在这棵树上做一些类似于 DP 的事情去统计路径的问题。由于保证了树高,因此我们可以有一些很暴力的操作,比如跳根链和暴力存子树内的每一个点之类的。

对于原题,我们先建出点分树,然后对每一个点开一个动态开点线段树维护点分树上子树内的所有点到当前点距离的权值。
例如说,我们对于询问距离 \(u\) 小于等于 \(x\) 的节点的权值和,我们就去枚举 \(v\) 表示 \(u\) 到点分树上根链的所有点。

于是就是询问 \(v\) 子树中到 \(u\) 距离不超过 \(x-dis(v,u)\) 的点的个数,这个过程相当于将 \(v\) 当作 lca 然后统计经过这个 lca 的点对的贡献。但是注意如果 \(v\) 是 lca 那么另一个点不能在 \(u\) 所在的那个子树中,因此要去掉在 \(u\) 那个方向的子树的贡献。我们再开一颗线段树将这个东西挂在对应方向的那个子树上,也就是每个点维护子树内每个点到父亲的距离的权值。
然后查询和修改的时候就暴力跳根链改对应线段树即可。复杂度 \(O(n\log n)\)

code

注意到需要求两点间距离,因此需要调用巨量次 lca,因此建议写一个 \(O(1)\) 查询的东西。普通的树上倍增太慢了。

自认为比较好看。

点击查看代码
#include<bits/stdc++.h>
bool Mbe;
using namespace std;
#define ll long long
//namespace FIO{
//	template<typename P>
//	inline void read(P &x){P res=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0' && ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}x=res*f;}
//	template<typename Ty,typename ...Args>
//	inline void read(Ty &x,Args &...args) {read(x);read(args...);}
//	inline void write(ll x) {if(x<0ll)putchar('-'),x=-x;static int sta[35];int top = 0;do {sta[top++] = x % 10ll, x /= 10ll;} while (x);while (top) putchar(sta[--top] + 48);}
//}
//using FIO::read;using FIO::write;
const int N=1e5+7;
int n,m,val[N],zx,siz[N],mxsiz[N],vis[N],F[N];
vector<int> q[N],p[N];
void Max(int &x,const int &y){x=max(x,y);}
void Min(int &x,const int &y){x=min(x,y);}
namespace LCA{
    int dfncnt=0,dfn[N],loc[N],f[N][17],Lg[N],dep[N];
    void dfs(int u,int fa){
        dfn[u]=++dfncnt,loc[dfn[u]]=u;f[dfn[u]][0]=dfn[fa];dep[u]=dep[fa]+1;
        for(int i=1;(1<<i)+1<=dfn[u];i++)f[dfn[u]][i]=min(f[dfn[u]][i-1],f[dfn[u]-(1<<(i-1))][i-1]);
        for(int v:q[u]){if(v==fa)continue;dfs(v,u);}
    }
    void init(){
        Lg[0]=-1;for(int i=1;i<=n;i++)Lg[i]=Lg[i/2]+1;
        dfs(1,0);
    }
    int get(int l,int r){int k=Lg[r-l+1];return min(f[r][k],f[l+(1<<k)-1][k]);}
    int lca(int x,int y){
        if(x==y)return x;
        if(dfn[x]>dfn[y])swap(x,y);
        return loc[get(dfn[x]+1,dfn[y])];
    }
    int getdis(int x,int y){return dep[x]+dep[y]-2*dep[lca(x,y)];}
}
using LCA::getdis;using LCA::lca;
void findrt(int u,int fa,int sumsiz){
    siz[u]=1;mxsiz[u]=0;
    for(int v:q[u]){
        if(v==fa||vis[v])continue;
        findrt(v,u,sumsiz);
        Max(mxsiz[u],siz[v]);
        siz[u]+=siz[v];
    }
    Max(mxsiz[u],sumsiz-siz[u]);
    if(mxsiz[u]<mxsiz[zx])zx=u;
}
void build_tr(int u,int fa){
    if(fa)p[fa].push_back(u),p[u].push_back(fa);
    vis[u]=1;
    for(int v:q[u]){
        if(vis[v])continue;
        mxsiz[0]=n,zx=0;
        findrt(v,u,siz[v]);build_tr(zx,u);
    }
}
struct sgt{
    int idcnt=0,rt[N],tr[N<<6],ls[N<<6],rs[N<<6];
    #define mid ((l+r)>>1)
    void push_up(int u){tr[u]=tr[ls[u]]+tr[rs[u]];}
    void modify(int &u,int l,int r,int x,int w){
        if(!u)u=++idcnt;
        if(l==r){tr[u]+=w;return;}
        x<=mid?modify(ls[u],l,mid,x,w):modify(rs[u],mid+1,r,x,w);
        push_up(u);
    }
    int query(int u,int l,int r,int ql,int qr){
        if(!u)return 0;
        if(ql<=l&&r<=qr)return tr[u];
        int res=0;
        if(ql<=mid)res+=query(ls[u],l,mid,ql,qr);
        if(qr>mid)res+=query(rs[u],mid+1,r,ql,qr);
        return res;
    }
}t1,t2;
vector<int>id[N];
void dfs(int u,int fa){
    id[u].push_back(u);F[u]=fa;
    for(int v:p[u]){
        if(v==fa)continue;
        dfs(v,u);
        for(int w:id[v])id[u].push_back(w);
    }
    for(int v:id[u]){
        t1.modify(t1.rt[u],0,n,getdis(u,v),val[v]);
        if(fa)t2.modify(t2.rt[u],0,n,getdis(fa,v),val[v]);
    }
}
bool Med;
signed main(){  //注意 vis!!!
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n>>m;for(int i=1;i<=n;i++)cin>>val[i];
    for(int i=1,u,v;i<n;i++)cin>>u>>v,q[u].push_back(v),q[v].push_back(u);
    LCA::init();mxsiz[0]=n,zx=0;findrt(1,0,n);int RT=zx;  //这个是点分树的真正的根
    build_tr(zx,0);dfs(RT,0);
    int lst=0;
    for(int i=1;i<=m;i++){
        int op,u,x;cin>>op>>u>>x;u^=lst,x^=lst;
        if(op==0){
            int ans=t1.query(t1.rt[u],0,n,0,x),bu=u;
            while(F[u]){
                if(getdis(F[u],bu)<=x)ans+=t1.query(t1.rt[F[u]],0,n,0,x-getdis(bu,F[u]))-t2.query(t2.rt[u],0,n,0,x-getdis(bu,F[u]));
                u=F[u];
            }
            cout<<ans<<'\n';lst=ans;
        }
        else{
            int bu=u;
            while(u){
                t1.modify(t1.rt[u],0,n,getdis(u,bu),x-val[bu]);
                if(F[u])t2.modify(t2.rt[u],0,n,getdis(F[u],bu),x-val[bu]);
                u=F[u];
            }
            val[bu]=x;
        }
    }
    cerr<<'\n'<<1e3*clock()/CLOCKS_PER_SEC<<"ms\n";
    cerr<<'\n'<<fabs(&Med-&Mbe)/1048576.0<<"MB\n";
    return 0;
}
posted @ 2025-11-16 19:45  all_for_god  阅读(10)  评论(0)    收藏  举报