树链剖分
问题1:
1,将树从x到y结点最短路径上所有节点的值都加上z
2,求树从x到y结点最短路径上所有节点的值之和
树链剖分(重链剖分)能很好解决这两点,复杂度$\mathcal{O(nlogn)} $
核心思想:
把每个节点所在子树最大的子节点定义为重儿子,与其相连的边为重边,其他为轻边
重链由重边组成,我们可以用数据结构来维护重链。
这样树就被剖分成了重链和轻边。
时间复杂度:
1.从根节点走到某一个点,其经过的轻重链个数\(\leq \log n\),证明:
从当前节点开始走,若当前节点与目标点不在一条重链,则会走一条轻边。轻边所连的儿子所在的子树大小 \(\leq\) 当前节点子树大小 \(/2\) ,所以走轻边不会超过\(\log n\)次。
若当前节点与目标点在一条重链,则停止。
2.如果(u,v)是一条轻边,那么size(v)<size(u)/2;
可以证明,树链剖分的时间复杂度为 $\mathcal{O(nlogn)} $
代码:(预处理+查询)
#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=200010;
int n,m,rt,p;
int h[N],e[M],ne[M],idx;
int size[N],f[N],d[N],son[N],rk[N],dfn[N],top[N];//子树大小、父节点、深度、重儿子、节点的遍历序、遍历序对应的节点、重链顶点
long long val[N],cnt;
int sum[4*N],la[4*N],l[4*N],r[4*N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs1(int fa,int u)//第一遍dfs,处理出d[]、f[]、size[]、son[]
{
int maxx=0;
d[u]=d[fa]+1;
f[u]=fa;
size[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
dfs1(u,j);
size[u]+=size[j];
if(size[j]>maxx){
maxx=size[j];
son[u]=j;
}
}
}
void dfs2(int fa,int u,int tp)//第二遍dfs,求出top、dfn、rk、
{
top[u]=tp;
dfn[u]=++cnt;
rk[cnt]=u;
if(!son[u])return;// 不要漏! ! ! ! !(不然直接卡死)
dfs2(u,son[u],tp);//先搜重儿子,保证重链的遍历序连续, 便于维护
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa||j==son[u])continue;//不能漏掉j==son[u]
dfs2(u,j,j);//若不是重儿子,i=top[i]
}
}
//线段树
void pushup(int u)
{
sum[u]=(sum[u<<1]+sum[u<<1|1])%p;
}
void build(int l1,int r1,int cur)
{
l[cur]=l1,r[cur]=r1;
if(l1==r1){
sum[cur]=val[rk[l1]]%p;
return;
}
int mid=l1+r1>>1;
build(l1,mid,cur<<1);
build(mid+1,r1,cur<<1|1);
pushup(cur);
}
void pushdown(int u)
{
if(l[u]==r[u]){
la[u]=0;
return;
}
sum[u<<1]=(sum[u<<1]+(r[u<<1]-l[u<<1]+1)*la[u]%p)%p;
sum[u<<1|1]=(sum[u<<1|1]+(r[u<<1|1]-l[u<<1|1]+1)*la[u]%p)%p;
la[u<<1]=(la[u<<1]+la[u])%p,la[u<<1|1]=(la[u<<1|1]+la[u])%p;
la[u]=0;
}
void modify(int l1,int r1,int cur,int v)
{
pushdown(cur);
if(r[cur]<l1||l[cur]>r1)return;
if(l[cur]>=l1&&r[cur]<=r1){
la[cur]=(la[cur]+v)%p;
sum[cur]=(sum[cur]+(r[cur]-l[cur]+1)*v%p)%p;
return;
}
modify(l1,r1,cur<<1,v);
modify(l1,r1,cur<<1|1,v);
pushup(cur);
}
int query(int l1,int r1,int cur)
{
pushdown(cur);
if(r[cur]<l1||l[cur]>r1)return 0;
if(l[cur]>=l1&&r[cur]<=r1){
return sum[cur];
}
return(query(l1,r1,cur<<1)+query(l1,r1,cur<<1|1))%p;
}
void change(int x,int y,int v)//将从x到y的路径的所有节点加上v
{
int fx=top[x],fy=top[y];
while(fx!=fy){//若不在一个重链上,就跳到重链顶端的父节点
if(d[fx]>=d[fy]){//不能写成d[x]>=d[y],因为要保证不能调到另一个点的上面
modify(dfn[fx],dfn[x],1,v);//修改所在重链的区间
x=f[fx];fx=top[x];
}
else{
modify(dfn[fy],dfn[y],1,v);
y=f[fy];fy=top[y];
}
}
if(dfn[x]>dfn[y])swap(x,y);
modify(dfn[x],dfn[y],1,v);//在同一条重链,修改他们之间的值即可
}
int seek(int x,int y)//查询类似
{
int res=0;
int fx=top[x],fy=top[y];
while(fx!=fy){
if(d[fx]>=d[fy]){
res=(res+query(dfn[fx],dfn[x],1))%p;
x=f[fx];fx=top[x];
}
else{
res=(res+query(dfn[fy],dfn[y],1))%p;
y=f[fy];fy=top[y];
}
}
if(dfn[x]>dfn[y])swap(x,y);
res=(res+query(dfn[x],dfn[y],1))%p;
return res;
}
void change2(int u,int v)//把以u为根节点的子树的所有节点加上u
{
modify(dfn[u],dfn[u]+size[u]-1,1,v);//因为一颗子树的dfs序是连续的,所以其dfs序在dfn[u]~dfn[u]+size[u]-1之间
}
int seek2(int u)//查询类似
{
return query(dfn[u],dfn[u]+size[u]-1,1);
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d%d%d",&n,&m,&rt,&p);
for(int i=1;i<=n;i++)scanf("%lld",&val[i]);
for(int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
dfs1(0,rt);
dfs2(0,rt,rt);
build(1,cnt,1);
while(m--){
int opt,x,y,z;
scanf("%d",&opt);
if(opt==1){
scanf("%d%d%d",&x,&y,&z);
change(x,y,z);
}
else if(opt==2){
scanf("%d%d",&x,&y);
printf("%d\n",seek(x,y));
}
else if(opt==3){
scanf("%d%d",&x,&z);
change2(x,z);
}
else{
scanf("%d",&x);
printf("%d\n",seek2(x));
}
}
return 0;
}
问题2 求k级祖先
若用 \(lca\) 或 重链剖分,查询复杂度为\(\mathcal{O(logn)}\).
若用长链剖分查询复杂度可以做到$\mathcal{O(1)} $
长链剖分,只是将重链的概念替换成长链,长儿子指所在子树深度最深的儿子。
因为长链剖分有一个很重要的性质:
求节点 \(u\) 的 \(k\) 级祖先,假设 \(2^i\leq k\leq 2^{i+1}\) 。
设节点 \(q\) 为 \(u\) 的 \(2^i\) 级祖先(倍增预处理)
则 \(len[top[q]]\) (节点 \(q\) 所在的长链的长度) \(\geq k-2^i\) 。
证明:
————————
于是我们在用一组vector来存每个top节点的len[top]级的父亲节点和儿子节点,就能解决问题了。
对于求k级儿子个数问题,有一个做法可以 $\mathcal{O(logn)} $求出。
将深度为\(d\)的节点用一组vector来存,因为要求某一子树中深度为d的,所以二分查找即可
#include<bits/stdc++.h>
using namespace std;
const int N=1000010,M=2000010;
int n,q,ans[N];
float lg;
int h[N],e[M],ne[M],idx;
int d[N],size[N],son[N],f[N][20],len[N];
int top[N],dfn[N],rk[N],cnt;
vector<int>up[N],down[N],sum[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return x;
}
//预处理
inline void dfs1(int fa,int u,int depth)
{
size[u]=1,len[u]=1,d[u]=depth;
f[u][0]=fa;
for(int i=1;(1<<i)<=depth;i++){
f[u][i]=f[f[u][i-1]][i-1];//倍增预处理,和lca一样
}
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa)continue;
dfs1(u,j,depth+1);
if(len[j]+1>len[u])son[u]=j,len[u]=len[j]+1;
size[u]+=size[j];
}
}
inline void dfs2(int u,int v)
{
top[u]=v;
dfn[u]=++cnt;
rk[cnt]=u;
sum[d[u]].push_back(cnt);
if(!son[u])return;
dfs2(son[u],v);
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==f[u][0]||j==son[u])continue;
dfs2(j,j);
}
}
inline void init()//用一组vector来存每个top节点的1~len[top]级的父亲节点和儿子节点
{
for(int p=1;p<=n;p++){
if(top[p]==p){
for(int i=0,j=p;i<len[p];i++,j=f[j][0])up[p].push_back(j);
for(int i=0,j=dfn[p];i<len[p];i++,j++)down[p].push_back(rk[j]);
}
}
}
inline int query(int u,int k)
{
if(!k)return 1;
int g=log2(k);
int p=f[u][g];
if(!p)return 0;
int c=k-(1<<g)+d[top[p]]-d[p];
int ff;
if(c>0)ff=up[top[p]][c];
else ff=down[top[p]][-c];//先确定k级祖先
if(!ff)return 0;
if(d[ff]+k>n)return 0;
int y=d[ff]+k;
//接下来就是求k级祖先ff所在子树深度为d[ff]+k的节点,二分查找即可
int l=0,r=sum[y].size()-1;
while(l<r){
int mid=l+r>>1;
if(sum[y][mid]>=dfn[ff])r=mid;
else l=mid+1;
}
int k1=l;
l=0,r=sum[y].size()-1;
while(l<r){
int mid=l+r+1>>1;
if(sum[y][mid]<=dfn[ff]+size[ff]-1)l=mid;
else r=mid-1;
}
return l-k1;
}
int main()
{
memset(h,-1,sizeof h);
n=read(),q=read();
for(int i=2;i<=n;i++){
int a;
a=read();
add(i,a),add(a,i);
}
dfs1(0,1,1);
dfs2(1,1);
init();
for(int i=0;i<q;i++){
int a,b;
a=read(),b=read();
ans[i]=query(a,b);
}
for(int i=0;i<q;i++)printf("%d ",ans[i]);
cout<<endl;
return 0;
}

浙公网安备 33010602011771号