树链剖分

树链剖分 

定义:

树链剖分,又称“重链剖分”。我们将树中的边分为轻边和重边。定义Size(x) 为以 x 为根的子树的节点个数,令 x 的儿子中 y 的Size()最大,那么,我们称边(x,y)为重边,y为x的重儿子。而由重边构成的链即为重链。

性质:

从根到某一点的路径上,轻边不超过O(logN)条,重链也不超过 O(logN)条。

实现:

进行两次DFS

1.找出各个节点的重儿子;

2.找出重链。

我们从根节点开始,向重儿子走,形成一条条重链;向轻儿子走,则以轻儿子开始,形成新一条条重链。

值得注意的是,我们在第二次DFS时,需要给各个节点重新编号,我们记为$nid[x]$,这样可以让一条重链的点的编号,是一段连续的区间,便于用数据结构维护。

小用处:求$LCA$

我们要求$lca(x,y)$,主需要将x,y不断滴沿着重链向上跳,知道他们俩属于同一条重链时,比较一下x,y深度,深度较小的即为$lca$。

例题: 

例题一:树的统计

一树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w。我们将以下面的形式来要求你对这棵树完成一些操作:
  CHANGE u t :把节点 u 权值改为 t;
  QMAX u v :询问点 u 到点 v 路径上的节点的最大权值;
  QSUM u v :询问点 u 到点 v 路径上的节点的权值和。
注意:从点 u 到点 v 路径上的节点包括 u 和 v 本身。
对于 100% 的数据 ,有 1≤n≤3×10e4,0≤q≤2×10e5 。
中途操作中保证每个节点的权值 w 在 −30000 至 30000 之间。

分析:

线段树分别维护两种询问:最大值以及权值和。然后询问一条路径时,只需要把该路径,分成若干条重链,即可用线段树维护以及查询了。

#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=400005;
int tt, las[N], ed[N<<1], nt[N<<1];
void add(int x,int y){
    ed[++tt]=y;nt[tt]=las[x];las[x]=tt;
}

int dep[N], Nid[N], fa[N], son[N], sz[N], Top[N], val[N], Label;

struct setment{int mx=-1e9,v=0;}tr[N<<3];
int Qsum(int p,int l,int r,int x,int y)
{
    if(x<=l && r<=y)return tr[p].v;
    int ret=0, mid=(l+r)>>1;
    if(x<=mid)ret += Qsum(p<<1, l, mid, x, y);
    if(y>mid)ret += Qsum(p<<1|1, mid+1, r, x, y);
    return ret;
}
int Qmax(int p,int l,int r,int x,int y)
{
    if(x<=l && r<=y)return tr[p].mx;
    int ret=-1e9, mid=(l+r)>>1;
    if(x<=mid)ret=max(ret, Qmax(p<<1, l, mid, x, y));
    if(y>mid)ret=max(ret, Qmax(p<<1|1, mid+1, r, x, y));
    return ret;
}
void modi(int p,int l,int r,int x,int d)
{
    if(l==x && x==r)
    {
        tr[p].mx=d;
        tr[p].v=d;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)modi(p<<1, l, mid, x, d);
    else modi(p<<1|1, mid+1, r, x, d);
    
    tr[p].v = tr[p<<1].v+tr[p<<1|1].v;
    tr[p].mx = max(tr[p<<1].mx, tr[p<<1|1].mx);
}

int n;
void hvedge(int x)
{
    dep[x]=dep[fa[x]]+1;
    sz[x]=1;
    for(re i=las[x];i;i=nt[i])
    {
        int v=ed[i];
        if(v!=fa[x])
        {
            fa[v]=x;
            hvedge(v);
            sz[x]+=sz[v];
            if(sz[v]>sz[son[x]])son[x]=v;
        }
    }
}
void hvline(int x,int ance)
{
    Top[x]=ance;
    Nid[x]=++Label;
    if(son[x]) hvline(son[x], ance);
    for(re i=las[x];i;i=nt[i])
    if(ed[i]!=son[x]&&ed[i]!=fa[x])hvline(ed[i],ed[i]);
}

int Qsum(int x,int y)
{
    int ret=0;
    while(Top[x]!=Top[y])
    {
        if(dep[Top[x]]<dep[Top[y]])swap(x,y);
        ret += Qsum(1, 1, n, Nid[Top[x]], Nid[x]);
        x=fa[Top[x]];
    }
    if(dep[x]>dep[y])swap(x, y);
    ret += Qsum(1, 1, n, Nid[x], Nid[y]);
    return ret;
}
int Qmax(int x,int y)
{
    int ret=-1e9;
    while(Top[x]!=Top[y])
    {
        if(dep[Top[x]]<dep[Top[y]])swap(x,y);
        ret = max(ret, Qmax(1, 1, n, Nid[Top[x]], Nid[x]));
        x=fa[Top[x]];
    }
    if(dep[x]>dep[y])swap(x, y);
    ret = max(ret, Qmax(1, 1, n, Nid[x], Nid[y]));
    return ret;
}

char ch[10];
int main()
{
    scanf("%d",&n);
    for(re i=1;i<n;++i)
    {
        int x, y;
        scanf("%d%d",&x,&y);
        add(x, y);
        add(y, x);
    }
    hvedge(1); hvline(1, 1);
    for(re i=1,x;i<=n;++i)
    {
        scanf("%d",&x);
        modi(1,1,n,Nid[i],x);
    }
    int T;scanf("%d",&T);
    while(T--)
    {
        int x, y;
        scanf("%s%d%d",ch,&x,&y);
        if(ch[0]=='Q'&&ch[1]=='M')printf("%d\n",Qmax(x, y));
        if(ch[0]=='Q'&&ch[1]=='S')printf("%d\n",Qsum(x, y));
        if(ch[0]=='C')modi(1,1,n,Nid[x],y);
    }
    return 0; 
}
View Code

例题二:种草

简要题意:已知一颗树有n个节点,我们会将一条路径经过的点的点权加一,或者求一条路径的点权综合。

分析:树链剖分+线段树维护区间和

#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=400005;
int tt, las[N], ed[N<<1], nt[N<<1];
inline void add(const int x,const int y)
{
    ed[++tt]=y;nt[tt]=las[x];las[x]=tt;
}

int dep[N], Nid[N], fa[N], son[N], sz[N], Top[N], Label;

int va[N<<3], lazy[N<<3];

inline void putdown(const int p)
{
    int x=lazy[p]; lazy[p]=0;
    int ls=(p<<1), rs=(p<<1|1);
    va[ls]+=x;     va[rs]+=x;
    lazy[ls]+=x; lazy[rs]+=x;
}

int Qsum(const int p,const int l,const int r,const int x,const int y)
{
    if(x<=l && r<=y)return va[p];
    
    if(lazy[p]) putdown(p);
    int ret=0, mid=(l+r)>>1;
    if(x<=mid)ret += Qsum(p<<1, l, mid, x, y);
    if(y>mid)ret += Qsum(p<<1|1, mid+1, r, x, y);
    return ret;
}

void modi(const int p,const int l,const int r,const int x,const int y)
{
    if(x<=l && r<=y)
    {
        va[p]++;
        lazy[p]++;
        return;
    }
    if(lazy[p])putdown(p);
    int mid=(l+r)>>1;
    if(x<=mid) modi(p<<1, l, mid, x, y);
    if(y>mid) modi(p<<1|1, mid+1, r, x, y);
    
    va[p] = va[p<<1] + va[p<<1|1];
}

int n;
void hvedge(const int x)
{
    dep[x]=dep[fa[x]]+1;
    sz[x]=1;
    for(re i=las[x];i;i=nt[i])
    {
        int v=ed[i];
        if(v!=fa[x])
        {
            fa[v]=x;
            hvedge(v);
            sz[x]+=sz[v];
            if(sz[v]>sz[son[x]])son[x]=v;
        }
    }
}
void hvline(const int x,const int ance)
{
    Top[x]=ance;
    Nid[x]=++Label;
    if(son[x]) hvline(son[x], ance);
    for(re i=las[x];i;i=nt[i])
    if(ed[i]!=son[x]&&ed[i]!=fa[x])hvline(ed[i],ed[i]);
}

inline int Qsum(int x,int y)
{
    int ret=0;
    while(Top[x]!=Top[y])
    {
        if(dep[Top[x]]<dep[Top[y]])swap(x, y);
        ret += Qsum(1, 1, n, Nid[Top[x]], Nid[x]);
        x=fa[Top[x]];
    }
    if(dep[x]>dep[y])swap(x, y);
    ret += Qsum(1, 1, n, Nid[x]+1, Nid[y]);
    return ret;
}
inline void modi(int x,int y)
{
    while(Top[x]!=Top[y])
    {
        if(dep[Top[x]]<dep[Top[y]])swap(x, y);
        modi(1, 1, n, Nid[Top[x]], Nid[x]);
        x=fa[Top[x]];
    }
    if(dep[x]>dep[y])swap(x, y);
    modi(1, 1, n, Nid[x]+1, Nid[y]);
}
char ch[10];
int main()
{
    int m;scanf("%d%d",&n,&m);
    for(re i=1;i<n;++i)
    {
        int x, y;
        scanf("%d%d",&x,&y);
        add(x, y);
        add(y, x);
    }
    hvedge(1); hvline(1, 1);
    while(m--)
    {
        int x, y;
        scanf("%s%d%d",ch,&x,&y);
        if(ch[0]=='P') modi(x, y);
        else printf("%d\n", Qsum(x, y));
    }
    return 0;
}
View Code

例题三:染色

给定一棵有个节点的无根树和m个操作,操作有2类:
1、将节点a到节点b路径上所有点都染成颜色c;
2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段)
如“112221”由3段组成:“11”、“222”和“1”。 请你写一个程序依次完成这m个操作。

分析:线段树维护区间颜色段数,同时记录区间左右端颜色,以助于求区间颜色段数。

#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=400005;
int tt,las[N],ed[N<<1],nt[N<<1],dep[N],Nid[N],fa[N],son[N],sz[N],Top[N],Label,col[N],a[N];
inline void add(const int x,const int y){ed[++tt]=y;nt[tt]=las[x];las[x]=tt;}
struct segment{int v,ls,rs,a,b,lazy;}tr[N<<3];
void update(int p)
{
    tr[p].ls=tr[p<<1].ls;tr[p].rs=tr[p<<1|1].rs;
    tr[p].v=tr[p<<1].v+tr[p<<1|1].v-(tr[p<<1].rs==tr[p<<1|1].ls);
}
void putdown(int p)
{
    int d=tr[p].lazy;tr[p].lazy=0;
    tr[p<<1].v=tr[p<<1|1].v=1;
    tr[p<<1].ls=tr[p<<1].rs=tr[p<<1|1].ls=tr[p<<1|1].rs=d;
    tr[p<<1].lazy=tr[p<<1|1].lazy=d;
}
void build(int p,int x,int y)
{
    tr[p].a=x;tr[p].b=y;
    if(x!=y)
    {
        int mid=(x+y)>>1;
        build(p<<1, x, mid);
        build(p<<1|1, mid+1, y);
        update(p);
    }
    else{tr[p].rs=tr[p].ls=col[x];tr[p].v=1;return;}
}
int query(int p,int x,int y)
{
    if(x<=tr[p].a&&tr[p].b<=y)return tr[p].v;
    if(tr[p].lazy)putdown(p);
    int mid=(tr[p].a+tr[p].b)>>1;
    if(x<=mid && y<=mid)return query(p<<1,x,y);
    if(mid<x && mid<y)return query(p<<1|1,x,y);
    return query(p<<1,x,y)+query(p<<1|1,x,y)-(tr[p<<1].rs==tr[p<<1|1].ls);
}
int gt(int p,int x,int d)
{
    if(tr[p].a==x&&x==tr[p].b)return(d==0?tr[p].ls:tr[p].rs);
    if(tr[p].lazy)putdown(p);
    int mid=(tr[p].a+tr[p].b)>>1;
    if(x<=mid)return gt(p<<1, x, d);
    return gt(p<<1|1, x, d);
}
void modi(int p,int x,int y,int d)
{
    if(x<=tr[p].a&&tr[p].b<=y){tr[p].v=1;tr[p].ls=tr[p].rs=tr[p].lazy=d;return;}
    if(tr[p].lazy)putdown(p);
    int mid=(tr[p].a+tr[p].b)>>1;
    if(x<=mid)modi(p<<1,x,y,d);
    if(y>mid)modi(p<<1|1,x,y,d);
    update(p);
}
void hvedge(int x)
{
    dep[x]=dep[fa[x]]+1;sz[x]=1;
    for(re i=las[x],v;i;i=nt[i])
    {
        v=ed[i];
        if(v!=fa[x])
        {
            fa[v]=x;
            hvedge(v);
            sz[x]+=sz[v];
            if(sz[v]>sz[son[x]])son[x]=v;
        }
    }
}
void hvline(int x,int ance)
{
    Top[x]=ance;Nid[x]=++Label;
    if(son[x]) hvline(son[x], ance);
    for(re i=las[x];i;i=nt[i])if(ed[i]!=son[x]&&ed[i]!=fa[x])hvline(ed[i],ed[i]);
}
int lca(int x,int y)
{
    while(Top[x]!=Top[y])
    {
        if(dep[Top[x]]<dep[Top[y]])swap(x, y);
        x=fa[Top[x]];
    }
    if(dep[x]>dep[y])swap(x, y);
    return x;
}
int getans(int x,int y)
{
    int ca=lca(x, y),ret=0;
    while(Top[x]!=Top[ca])
    {
        ret+=query(1,Nid[Top[x]],Nid[x])-(gt(1,Nid[Top[x]],0) == gt(1,Nid[fa[Top[x]]],1));
        x=fa[Top[x]];
    }
    while(Top[y]!=Top[ca])
    {
        ret+=query(1,Nid[Top[y]],Nid[y])-(gt(1,Nid[Top[y]],0) == gt(1,Nid[fa[Top[y]]],1));
        y=fa[Top[y]];
    }
    if(dep[x]>dep[y])swap(x,y);
    ret+=query(1,Nid[x],Nid[y]);
    return ret;
}
void Change(int x,int y,int d)
{
    while(Top[x]!=Top[y])
    {
        if(dep[Top[x]]<dep[Top[y]])swap(x, y);
        modi(1,Nid[Top[x]],Nid[x],d);
        x=fa[Top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    modi(1,Nid[x],Nid[y],d);
}
char ch[4];
int main()
{
    int m,n;scanf("%d%d",&n,&m);
    for(re i=1;i<=n;++i)scanf("%d",&a[i]);
    for(re i=1, x, y;i<n;++i)
    {
        scanf("%d%d",&x,&y);
        add(x, y);
        add(y, x);
    }hvedge(1);hvline(1, 1);
    for(re i=1;i<=n;++i)col[Nid[i]]=a[i];
    build(1, 1, n);int x,y,d;
    while(m--)
    {
        scanf("%s",ch);
        if(ch[0]=='Q')
        {
            scanf("%d%d",&x,&y);
            printf("%d\n", getans(x, y));
        }
        else
        {
            scanf("%d%d%d",&x,&y,&d);
            Change(x, y, d);
        }
    }
    return 0;
}
View Code

例题四:旅行

S国有N个城市,编号从1到N。
城市间用N-1条双向道路连接,满足从一个城市出发可以到达其它所有城市。
每个城市信仰不同的宗教,如飞天面条神教、隐形独角兽教、绝地教都是常见的信仰。
为了方便,我们用不同的正整数代表各种宗教。 S国的居民常常旅行。
旅行时他们总会走最短路,并且为了避免麻烦,只在信仰和他们相同的城市留宿。
当然旅程的终点也是信仰与他相同的城市。
S国政府为每个城市标定了不同的旅行评级,旅行者们常会记下途中(包括起点和终点)留宿过的城市的评级总和或最大值。 在S国的历史上常会发生以下几种事件: •
"CC x c":城市x的居民全体改信了c教; • "CW x w":城市x的评级调整为w; • "QS x y":一位旅行者从城市x出发,到城市y,并记下了途中留宿过的城市的评级总和; • "QM x y":一位旅行者从城市x出发,到城市y,并记下了途中留宿过的城市的评级最大值。 由于年代久远,旅行者记下的数字已经遗失了,但记录开始之前每座城市的信仰与评级,还有事件记录本身是完好的。
请根据这些信息,还原旅行者记下的数字。 为了方便,我们认为事件之间的间隔足够长,以致在任意一次旅行中,所有城市的评级和信仰保持不变。

分析:各个宗教分别进行线段树维护,但需要动态开点以节约空间

#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=8e5+5;
int tt, las[N], ed[N<<1], nt[N<<1];
void add(int x,int y){ed[++tt]=y;nt[tt]=las[x];las[x]=tt;}
int dep[N], fa[N], Top[N], son[N], sz[N], Nid[N], Label;
void hvedge(int x)
{
    dep[x]=dep[fa[x]]+1;
    sz[x]=1;
    for(re i=las[x];i;i=nt[i])
    {
        int v=ed[i];
        if(v!=fa[x])
        {
            fa[v]=x;hvedge(v);sz[x]+=sz[v];
            if(sz[v]>sz[son[x]])son[x]=v;
        }
    }
}
void hvline(int x,int ance)
{
    Top[x]=ance;Nid[x]=++Label;
    if(son[x])hvline(son[x], ance);
    for(re i=las[x];i;i=nt[i])if(ed[i]!=fa[x]&&ed[i]!=son[x])hvline(ed[i], ed[i]);
}
int seg;
struct segment{int ls, rs, v, mx;}tr[N*32];
void update(int p)
{
    tr[p].mx=tr[p].v=0;
    if(tr[p].ls)tr[p].v=tr[p].v+tr[tr[p].ls].v, tr[p].mx=max(tr[p].mx, tr[tr[p].ls].mx);
    if(tr[p].rs)tr[p].v=tr[p].v+tr[tr[p].rs].v, tr[p].mx=max(tr[p].mx, tr[tr[p].rs].mx);
}
void modi(int &p,int l,int r,int x,int d)
{
    if(!p)p=++seg;
    if(l==r){tr[p].mx=tr[p].v=d;return;}
    int mid=(l+r)>>1;
    if(x<=mid)modi(tr[p].ls, l, mid, x, d);
    else modi(tr[p].rs, mid+1, r, x, d);
    update(p);
}
int Qsum(int p,int l,int r,int x,int y)
{
    if(!p)return 0;
    if(x<=l&&r<=y)return tr[p].v;
    int mid=(l+r)>>1, ret=0;
    if(x<=mid)ret+=Qsum(tr[p].ls, l, mid, x, y);
    if(y>mid)ret+=Qsum(tr[p].rs, mid+1, r, x, y);
    return ret;
}
int Qmax(int p,int l,int r,int x,int y)
{
    if(!p)return 0;
    if(x<=l&&r<=y)return tr[p].mx;
    int mid=(l+r)>>1, ret=0;
    if(x<=mid)ret=max(ret, Qmax(tr[p].ls, l, mid, x, y));
    if(y>mid)ret=max(ret, Qmax(tr[p].rs, mid+1, r, x, y));
    return ret;
}
int n, w[N], c[N], root[N];
int Qsum(int x, int y)
{
    int bl=c[x], ret=0;
    while(Top[x]!=Top[y])
    {
        if(dep[Top[x]]<dep[Top[y]])swap(x, y);
        ret += Qsum(root[bl], 1, n, Nid[Top[x]], Nid[x]);
        x=fa[Top[x]];
    }
    if(dep[x]>dep[y])swap(x, y);
    ret += Qsum(root[bl], 1, n, Nid[x], Nid[y]);
    return ret;
}
int Qmax(int x,int y)
{
    int bl=c[x], ret=0;
    while(Top[x]!=Top[y])
    {
        if(dep[Top[x]]<dep[Top[y]])swap(x, y);
        ret = max(ret, Qmax(root[bl], 1, n, Nid[Top[x]], Nid[x]));
        x=fa[Top[x]];
    }
    if(dep[x]>dep[y])swap(x, y);
    ret = max(ret, Qmax(root[bl], 1, n, Nid[x], Nid[y]));
    return ret;
}
char ch[5];
int main()
{
    int m;
    scanf("%d%d",&n,&m);
    for(re i=1;i<=n;++i)scanf("%d%d",&w[i],&c[i]);
    for(re i=1, x, y;i<n;++i)
    {
        scanf("%d%d",&x,&y);
        add(x, y);add(y, x);
    }
    hvedge(1);hvline(1,1);
    for(re i=1;i<=n;++i)modi(root[c[i]], 1, n, Nid[i], w[i]);
    
    while(m--)
    {
        int x, y;scanf("%s%d%d",ch,&x,&y);
        if(ch[0]=='C'&&ch[1]=='C'&&c[x]!=y)
        {
            modi(root[c[x]], 1, n, Nid[x], 0);
            c[x]=y;
            modi(root[c[x]], 1, n, Nid[x], w[x]);
        }
        if(ch[0]=='C'&&ch[1]=='W'&&w[x]!=y)
        {
            modi(root[c[x]], 1, n, Nid[x], y);
            w[x]=y;
        }
        if(ch[0]=='Q'&&ch[1]=='S')printf("%d\n",Qsum(x, y));
        if(ch[0]=='Q'&&ch[1]=='M')printf("%d\n",Qmax(x, y));
    }
    return 0;
}
View Code

 

posted @ 2021-08-17 21:53  kzsn  阅读(128)  评论(0)    收藏  举报