长链剖分

与重链剖分类似,但是重儿子是根据深度判定,可以处理深度相关问题。具体的,继承重儿子的信息,暴力合并其他轻儿子的链。由于所有链长为 \(n\),且每条链只会在链头被合并,所以时间复杂度为线性。

有性质:

  • 自己祖先的链长一定大于自己当前链长。由此可以推出 \(x\)\(k\) 级祖先的链长大于等于 \(len_x+k\)

  • 从任意一个节点出发,到达根节点至多根号条不同的链。由于每次跳到不同的链则新链链长大于原先的链,于是依次最少使用 \(1,2,3,\cdots\),故最多根号条链。

COGS 3184 收益

长链剖分模板,选最大的 \(k\) 条链即可。

CF1009F

长链剖分模板,继承重儿子加上轻儿子的数组即可做到线性。

COGS 2652 秘术「天文密葬法」

首先可以看出这题是分数规划,二分 \(mid\) 则转为判定性问题判定是否存在长为 \(m\) 的路径,使得 \(\sum a_i - mid \times \sum b_i \le 0\),转为长链剖分。但是在继承重儿子的时候需要整个数组加值,可以维护当前数组总共加过 \(p\),在加入新点 \(a_x\) 的时候,将 \(a_x\) 设置为 \(-p\),然后将 \(p\) 加上需要加的值,查询的时候真实值就为 \(a+p\)

P5904 [POI 2014] HOT-Hotels 加强版

维护 \(f_{x,i}\)\(x\) 子树内,距离 \(x\)\(i\) 的点个数,\(g_{x,i}\) 表示 \(x\) 子树内,点对 \((u,v)\) 满足 \(dis(u,lca)=dis(v,lca) \land dis(x,lca)+j=dis(u,lca)\) 的个数。

则有转移:

\[\begin{aligned} ans &\leftarrow g_{x,0} \\ ans &\leftarrow \sum_{u,v \in son(x) \land u \ne v} f_{u,j-1} g_{v,j+1} \\ f_{x,j} &\leftarrow f_{u,j-1} \\g_{x,j} &\leftarrow \sum_{u,v \in son(x) \land u \ne v} f_{u,j-1} f_{v,j-1} \\g_{x,j} &\leftarrow g_{u,j+1} \end{aligned} \]

使用指针继承维护,贴个指针维护代码。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INT_MAX (int)(1e18)

const int N=1e5+10;

int n;
int head[N],nxt[N<<1],ver[N<<1];

inline 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<<3)+(x<<1)+(ch^48),ch=getchar();
    return x*f;
}

int len[N],son[N];

void dfs(int u,int v){
    for(int i=head[u];i;i=nxt[i]){
        int to=ver[i];
        if(to==v) continue;
        dfs(to,u);
        if(len[to]>len[son[u]]) son[u]=to;
    }
    len[u]=len[son[u]]+1;
}

int ans;
int tmp[N<<2],*f[N],*g[N],*id=tmp;

void dfs1(int u,int v){
    f[u][0]=1;
    if(son[u]) f[son[u]]=f[u]+1,g[son[u]]=g[u]-1,dfs1(son[u],u);
    ans+=g[u][0];
    for(int i=head[u];i;i=nxt[i]){
        int dao=ver[i];
        if(dao==v||dao==son[u]) continue;
        f[dao]=id,id+=len[dao]<<1,g[dao]=id,id+=len[dao]<<1,dfs1(dao,u);
        for(int j=1;j<=len[dao];j++)
            ans+=f[dao][j-1]*g[u][j];
        for(int j=0;j<len[dao]-1;j++)
            ans+=f[u][j]*g[dao][j+1];
        for(int j=0;j<len[dao];j++)
            g[u][j+1]+=f[dao][j]*f[u][j+1],f[u][j+1]+=f[dao][j];
        for(int j=1;j<len[dao];j++) g[u][j-1]+=g[dao][j];
    }
}

signed main(){
    n=read();
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        nxt[i]=head[u],head[u]=i,ver[i]=v;
        nxt[i+n]=head[v],head[v]=i+n,ver[i+n]=u;
    }
    dfs(1,0);
    f[1]=id,id+=len[1]<<1,g[1]=id,id+=len[1]<<1;
    dfs1(1,0);
    cout<<ans<<"\n";
    return 0;
}

P5903 【模板】树上 K 级祖先

根据性质,我们可以对于每条链链顶,记录其沿链长向下链长个点和其向上链长个点的祖先信息。然后倍增预处理每个点的 \(2^x\) 级祖先。则查询 \(x\)\(y\) 级祖先时,可以先将 \(x\)\(2^{\log y}\) 步,则此时链长大于 \(2^{\log y}\),同时也大于 \(y- 2^{\log y}\)。接着将 \(x\) 跳到链顶,检查此时 \(y\) 剩余正负,对应向上向下跳即可做到 \(O(n \log n) - O(1)\)

posted @ 2026-07-01 23:51  ask_silently  阅读(5)  评论(0)    收藏  举报