【笔记】长链剖分

性质

性质1:对树长链剖分后,树上所有长链的长度和为\(n\)

性质2:对于树上任意一点\(x\),它的\(K\)级祖先\(y\)所在长链的长度一定\(\ge K\)

性质3:从任何一个点向上跳到根所经过的轻边不会超过\(\sqrt n\)

适用范围:

1.维护子树中只与深度有关的信息,\(O(n)\)统计每个点子树中可合并的以深度为下标的信息。

2.单次\(O(1)\)在线查询一个点的\(k\)级祖先。

实现:

按照子树深度剖分,与深度最深的儿子连成链

对于重儿子用\(O(1)\)时间继承信息,轻儿子直接改

inline void dfs1(re int x,re int prt,re int depth){
    re int i,y;maxd[x]=dep[x]=depth;fa[x]=prt;size[x]=1;
    for(i=h[x];i;i=e[i].next){
        y=e[i].to;if(y==prt)continue;
        dfs1(y,x,depth+1);size[x]+=size[y];
        if(maxd[y]>maxd[x]){maxd[x]=maxd[y];son[x]=y;}
    }
}
inline void dfs2(re int x,re int Top){
    re int i,y;top[x]=Top;st[x]=++tot;
    if(!son[x])return;
    dfs2(son[x],Top);
    for(i=h[x];i;i=e[i].next){
        y=e[i].to;if(y==prt||y==son[x])continue;
        dfs(y,y);
    }
}

运用:

1.\(O(1)\)\(K\)级祖先

第一步预处理

  • \(ST\)预处理出每个点的\(2^K\)级祖先
  • 对于所有\(K(1-n)\) ,我们把这个\(K\)转换成二进制表示,记录它的位数\(log_K\)
    显然有\(2log_K\geq \frac{K}{2}\)
  • \(len_x\)为节点\(x\)所属长链的长度,对于所有长链的顶端节点\(tp\),我们记录\(tp\)上方和下方的\(len_{tp}−1\)个节点。
void dfs1(re int x,re int prt,re int depth){
    re int i,y;
    *dp[x]=prt;maxd[x]=dep[x]=depth;
    for(i=h[x];i;i=e[i].next){
        y=e[i].to;if(y==prt)continue;
        dfs1(y,x,depth+1);
        if(maxd[x]<maxd[y]){maxd[x]=maxd[y];son[x]=y;}
    }
}
void dfs2(re int x,re int Top){
    re int i,y;top[x]=Top;
    len[x]=maxd[x]-dep[Top]+1;
    if(!son[x])return;
    dfs2(son[x],Top);
    for(i=h[x];i;i=e[i].next){y=e[i].to;if(y==*dp[x]||y==son[x])continue;dfs2(y,y);}
}
inline void ST(void){
    re int i,j;
    for(j=1;j<=18;++j)
        for(i=1;i<=n;++i)dp[i][j]=dp[dp[i][j-1]][j-1];
    *tot=-1;for(i=1;i<=n;++i)tot[i]=tot[i>>1]+1;
}
inline int LCA(re int x,re int y){
    re int i;
    if(dep[x]<dep[y])swap(x,y);
    for(i=18;~i;--i)if(dep[dp[x][i]]>=dep[y])x=dp[x][i];
    if(x==y)return x;
    for(i=18;~i;--i)if(dp[x][i]!=dp[y][i]){x=dp[x][i];y=dp[y][i];}
    return *dp[x];
}
inline void Inc(void){
    re int i,j,tp,sum,tmp;sum=0;
    for(i=1;i<=n;++i){
        tp=top[i];if(vis[tp])continue;vis[tp]=1;
        idx[tp]=sum+len[tp];save[idx[tp]]=tp;
        tmp=tp;for(j=idx[tp]-1;j>idx[tp]-len[tp];--j){tmp=son[tmp];save[j]=tmp;}
        tmp=tp;for(j=idx[tp]+1;j<idx[tp]+len[tp];++j){tmp=*dp[tmp];save[j]=tmp;}
        sum+=2*len[tp]-1;
    }
} 

然后处理询问:

设询问的点是\(x\),要找它的\(K\)级祖先\(fa\)

首先利用\(ST\)从询问点\(x\)上跳 \(2^{log_K}\)步,把上跳后这个点叫\(y\)

那么因为\(2^{log_K}\geq \frac{K}{2}\),我们至少跳了\(K \over 2\)步。

此时根据性质\(2\)\(y\)所在长链的长度\(len_y\geq \frac{K}{2}\)

\(tp\)\(y\)所在长链的顶端节点。

则在\(tp\)上方的第\((K−2^{log_K})−Dis(y,tp)\)个点。(负数为下方,\(0\)就是\(tp\)本身)

} 
inline int Query(re int x,re int k){
    re int tp;if(k>dep[x])return 0;if(!k)return x;
    x=dp[x][tot[k]];k-=1<<tot[k];
    tp=top[x];return save[idx[tp]+k-(dep[x]-dep[tp])];  
}

模板题

\(BSOJ5187\)or\(BSOJ5385\)

因为\(k\)的大小不确定,所以分情况来做。设\(Lim=\sqrt{n}\),当\(k\le Lim\)时,可以预处理出\(sum[i][j]\)表示\(i\)节点以\(j\)步伐一直往上走,直到走到根节点(\(i\)节点的深度如果不是\(k\)的倍数,根节点不计入)为止能得到的点权和,查询时直接求就好了.当\(k>Lim\)时,就要暴力往上走了用长链剖分\(O(1)\)查询一个点的k级祖先,因此最多爬\(\sqrt{n}\)次;

单次查询时间复杂度\(O(\sqrt{n})\)

2.\(O(n)\)继承信息优化\(dp\)

\(BSOJ6308\)

我们先表示出要求的东西,我们想办法把\(x\)的距离\(\leq k\)的点权和转化为与\(x\)的子树中到\(x\)的距离\(\leq k\)的点权和

\(\displaystyle{f_{x,k}}\)表示在\(x\)的子树中到\(x\)的距离\(\leq k\)的点权和

\(fa^x_k表示\)x\(的\)k$级父亲

\(\displaystyle{Ans_{x,K}=f_{x,K}+\sum_{k=1}^{\min{\{dep_x,K\}}}f_{fa^x_k,K-k}-f_{x,K-2k}}\)

后面的是对于每一个\(x\)\(k\)级父亲,我们求出在它的子树中但又不在\(x\)的子树中的,距离它\(K-k\)距离的点权和

因此也就等价于,在\(x\)上面距离\(x\)不超过\(K\)的距离和

用这道题来讲一下如何使用长链剖分

为了能\(O(n)\)完成重儿子继承,我们换一种设法

新设\(f_{id_x+k}\)表示\(x\)子树内距离不超过\(k\)的点权和

这样设有什么好处呢,对于\(x\)的重儿子\(y\),\(f_{id_x+k}=f_{(id_y+1)+k}=f_{id_y+(k+1)}\),不用赋值就继承了来自下方的信息

然后就可以转移了,贴一下老师的分析

重儿子直接继承完毕后:

\(j=0,f_{id_x}=a_x\)

\(1\leq j\leq len_x,f_{id_x+j}+=a_x\)

设轻儿子为\(y\)\(x\)点权值为\(a_x\),暴力维护时注意判断一下\(j\)\(len_y\)的大小

\(1\leq j\leq len_{y}+1,则f_{id_x+j}+=f_{id_y+j-1}\)

\(len_{y}+2\leq j\leq len_{x},则f_{id_x+j}+=f_{id_y+len_{y}}\)

\(修改一下形式:\)

\(1\leq j\leq len_{y}+1,则f_{id_x+j}+=f_{id_y+j-1}-f_{id_y+len_{y}};\)

\(1\leq j\leq len_{x},则f_{id_x+j}+=f_{id_y+len_{y}}\)

重链的循环需要优化掉:

\(将f_{id_y+leny}累积到sum_{id_x}中\)

\(1\leq j\leq len_{y}+1,则f_{id_x+j}+=f_{id_y+j-1}-f_{id_y+len_{y}};\)

\(sum_{id_x}+=f_{id_y+len_{y}}\)

\(由于f_{id_x+0}不需要累加sum_{id_x},所以需要先提前减去,于是\)

\(f_{id_x}-=-sum_{id_x}\)

\(sum_{id_x}+=w_{x}\)

\(0\leq j\leq len_{x},f_{id_x+j}+=sum_{id_x}\)

\(现在尝试优化掉最后一个重链循环(因为只有轻链可以暴力维护,重链暴力维护时间复杂度会变化):\)

\(实际上只需要在使用f数组的时候和sum数组打包即可,不需要每次把sum数组加入到f数组的结果中,此时相当于把原本的f拆成了f和sum两部分。注意,此时重儿子直接继承过来f时,sum没有直接继承过来,所以现在要另外对sum进行继承了\)

\(重儿子f数组直接继承,sum数组现在需要单独继承\)

\(sum_{id_x}=sum_{id_{son_{x}}}\)

\(1\leq j\leq len_{y}+1,则f_{id_x+j}+=f_{id_y+j-1}+sum_{id_y}-f_{id_y+leny}-sum_{id_y};\)

\(原本所有使用f的地方都变成f+sum\)

\(sum_{id_x}+=f_{id_y+leny}+sum_{id_y}\)

\(f_{id_x}-=sum_{id_x}\)

\(sum_{id_x}+=w_{x}\)

\(同时更新答案时使用f_{id_x+k}+sum_{id_x}即可\)

inline void Solve(re int x){
    re int i,j,y,leny;if(son[x]){Solve(son[x]);sum[idx[x]]=sum[idx[son[x]]];}
    for(i=h[x];i;i=e[i].next){
        y=e[i].to;
        if(y==fa[x]||y==son[x])continue;
        Solve(y);leny=maxd[y]-dep[y];
        for(j=1;j<=leny+1;++j)dp[idx[x]+j]+=dp[idx[y]+j-1]+sum[idx[y]]-(dp[idx[y]+leny]+sum[idx[y]]);
        sum[idx[x]]+=dp[idx[y]+leny]+sum[idx[y]];
    }
    dp[idx[x]]-=sum[idx[x]];sum[idx[x]]+=a[x];
    for(i=hq[x];i;i=eq[i].next){
        y=eq[i].to;
        ans[y]+=eq[i].opt*(sum[idx[x]]+dp[min(1ll*idx[x]+eq[i].k,1ll*idx[x]+maxd[x]-dep[x])]);
    }
}

猜你喜欢

转载自www.cnblogs.com/66t6/p/11707687.html
今日推荐