树链剖分[学习笔记]

树链剖分

壹.

树剖,就是树链剖分,将一棵树剖分成一堆链 (如说 \(\dots\) )

本文主要介绍重链剖分。

树剖成链之后一段重链上的 \(dfs\) 序是连续的,那么我们就可以对 \(dfs\) 序使用一些数据结构(树状数组、线段树等)


\(1\).一些变量及意义

  • \(fa[x]\) \(x\) 的父节点
  • \(depth[x]\) \(x\) 的深度
  • \(siz[x]\) \(x\) 的子树大小,我们将根据他求重儿子
  • \(wson[x]\) \(x\) 的重儿子,即 \(x\) 的儿子中子树最大的
  • \(top[x]\) \(x\) 所在重链的链顶
  • \(dfn[x]\) \(x\) 的 \(dfs\) 序,即第二次访问到 \(x\) 的时间
  • \(id[i]\) 用来求 \(dfs\) 序为 \(i\) 的节点,即 \(id[dfn[x]]=x\)
  • \(out[x]\) 第二次访问到 \(x\) 的时间

说白了,树剖就是用来求上述东西,然后就可以利用上述东西搞事情了。


\(2\).两遍 \(dfs\) 完成树剖

  • 第一遍,我们可以轻松求出 \(fa[x]\),\(depth[x]\),\(siz[x]\),\(wson[x]\)。
cpp 复制代码
void dfs(int x){
    depth[x]=depth[fa[x]]+1;
    siz[x]=1;
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].to;
        if(y==fa[x])  continue;
        fa[y]=x;
        dfs(y);
        if(siz[y]>siz[wson[x]])  wson[x]=y;//更新重儿子
        siz[x]+=siz[y];
    }
}

偷的图

  • 第二遍,主要求 \(dfs\) 序,顺便求下 \(top\) 。我们优先跑重儿子,就可以保证重链上的 \(dfs\) 序连续。
cpp 复制代码
void dfs(int x,int tp){
    top[x]=tp;
    dfn[x]=++t;id[t]=x;
    if(wson[x])  dfs(wson[x],tp);
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].to;
        if(y==fa[x]||y==wson[x])  continue;
        dfs(y,y);//显然,轻儿子一定是重链的链顶
    }
    out[x]=t;
}

\(3\).求 \(LCA\)

我们从 \(u,v\) 分别上跳,跳到链顶,为了防止它们 像小H和小C一样 错过,所以我们每次选择链顶深度大的上跳,到一条链上后,深度小的即为 \(LCA\)。

cpp 复制代码
int LCA(int u,int v){
    while(top[u]!=top[v]){
        if(depth[top[u]]<depth[top[v]])  swap(u,v);
        u=fa[top[u]];
    }
    return (depth[u]<depth[v] ? u : v);
}

跳重链的复杂度为 \(O(\log _2n)\) 但是它严格跑不满,所以它的速度薄纱倍增和 \(tarjan\) 。


\(4\).一些性质和利用

  • 树剖成链之后一段重链上的 \(dfs\) 序是连续的,用线段树等即可优秀地维护重链信息
  • 从任意节点跳到根节点最多跳 \(\log _2n\) 次,多数情况下跑不满。

之前总听人说树剖码量大,学了之后才发现树剖就那么几行,不太会变,真正码量大的是维护重链信息的数据结构------比如线段树。如果你对线段树很怵头,先学好线段树吧,或者说,打完树剖你也就不怵头了。


贰.\(hs\)题单

\(T_A\) ZJOI2008 树的统计

板子,树剖,线段树维护单点修改,最大值和区间和,跳重链求解即可。

注意此题值域 \([-30000,30000]\) ,求最大值时注意初值赋极小
CODE

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define swap(x,y) (x^=y,y^=x,x^=y)
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
const int N=3*1e4+10;
#define INF 30010
int n;
int w[N];
struct EDGE{int next,to;}e[N<<1];
int head[N],total;
void add(int u,int v){e[++total]={head[u],v};head[u]=total;}

namespace Tree_Chain_Partition{
    int fa[N],depth[N],wson[N],siz[N];
    int top[N],dfn[N],id[N],out[N],t;
    void dfs1(int x){
        depth[x]=depth[fa[x]]+1;
        siz[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x])  continue;
            fa[y]=x;
            dfs1(y);
            if(siz[y]>siz[wson[x]])  wson[x]=y;
            siz[x]+=siz[y];
        }
    }
    void dfs2(int x,int tp){
        top[x]=tp;
        dfn[x]=++t;id[t]=x;
        if(wson[x])  dfs2(wson[x],tp);
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x]||y==wson[x])  continue;
            dfs2(y,y);
        }
        out[x]=t;
    }
    int LCA(int u,int v){
        while(top[u]!=top[v]){
            if(depth[top[u]]<depth[top[v]])  swap(u,v);
            u=fa[top[u]];
        }
        return (depth[u]<depth[v]?u:v);
    }
} using namespace Tree_Chain_Partition;

namespace Segment_Tree{
    struct Tree{
        int sum,mx,l,r;
        #define l(i) tr[i].l
        #define r(i) tr[i].r
        #define sum(i) tr[i].sum
        #define mx(i) tr[i].mx
        #define ls(i) (i<<1)
        #define rs(i) (i<<1|1)
    }tr[N<<2];
    void pushup(int i){
        sum(i)=sum(ls(i))+sum(rs(i));
        mx(i)=max(mx(ls(i)),mx(rs(i)));
    }
    void build(int i,int l,int r){
        l(i)=l;r(i)=r;
        if(l==r){
            mx(i)=sum(i)=w[id[l]];
            return;
        }
        int mid=(l+r)>>1;
        build(ls(i),l,mid);build(rs(i),mid+1,r);
        pushup(i);
        return;
    }
    void modify(int i,int x,int k){
        if(l(i)==r(i)){
            sum(i)=mx(i)=k;
            return;
        }
        int mid=(l(i)+r(i))>>1;
        if(x<=mid)  modify(ls(i),x,k);
        else  modify(rs(i),x,k);
        pushup(i);
        return;
    }
    int asksum(int i,int ql,int qr){
        int l=l(i),r=r(i);
        if(ql<=l&&r<=qr)  return sum(i);
        int mid=(l+r)>>1,res=0;;
        if(ql<=mid)  res+=asksum(ls(i),ql,qr);
        if(mid<qr)  res+=asksum(rs(i),ql,qr);
        return res;  
    }
    int askmax(int i,int ql,int qr){
        int l=l(i),r=r(i);
        if(ql<=l&&r<=qr)  return mx(i);
        int mid=(l+r)>>1,res=-INF;
        if(ql<=mid)  res=max(res,askmax(ls(i),ql,qr));
        if(mid<qr)  res=max(res,askmax(rs(i),ql,qr));
        return res;
    }
}using namespace Segment_Tree;
int ans;

int qsum(int u,int lca){
    int res=0;
    while(depth[top[u]]>depth[lca]&&top[u]!=top[lca]){
        res+=asksum(1,dfn[top[u]],dfn[u]);
        u=fa[top[u]];
    }
    res+=(u==lca?w[u]:asksum(1,dfn[lca],dfn[u]));
    return res;
}

int qmax(int u,int lca){
    int res=-INF;
    while(depth[top[u]]>depth[lca]&&top[u]!=top[lca]){
        res=max(res,askmax(1,dfn[top[u]],dfn[u]));
        u=fa[top[u]];
    }
    res=max(res,(u==lca?w[u]:askmax(1,dfn[lca],dfn[u])));
    return res;
}

signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    n=read;
    for(int i=1,a,b;i<n;i++)  a=read,b=read,add(a,b),add(b,a);
    for(int i=1;i<=n;i++)  w[i]=read;
    dfs1(1);dfs2(1,1);
    build(1,1,t);
    int T;T=read;
    char op[7];int u,v,k;
    while(T-->0){
        scanf("%s",op);u=read;
        if(op[0]=='C'){
            k=read;w[u]=k;
            modify(1,dfn[u],k);
        }
        else{
            v=read;
            int lca=LCA(u,v);
            if(op[1]=='S'){
                ans=-w[lca];
                ans+=qsum(u,lca);
                ans+=qsum(v,lca);
            }
            else{
                ans=-INF;
                ans=max(ans,qmax(u,lca));
                ans=max(ans,qmax(v,lca));
            }
            write(ans),pt;
        }
    }

    return 0;
}

\(T_B\) HAOI2015 树上操作

板子,树剖,线段树维护区间修改区间求和。
CODE

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
#define N 100010
int n,w[N];
int m;

namespace Tree_Chain_Partition{
    struct EDGE{int next,to;}e[N<<1];
    int head[N],total;
    void add(int u,int v){e[++total]={head[u],v};head[u]=total;}
    int fa[N],wson[N],depth[N],siz[N];
    int top[N],dfn[N],out[N],id[N],t;
    void dfs1(int x){
        depth[x]=depth[fa[x]]+1;
        siz[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x])  continue;
            fa[y]=x;
            dfs1(y);
            if(siz[y]>siz[wson[x]])  wson[x]=y;
            siz[x]+=siz[y];
        }
    }
    void dfs2(int x,int tp){
        top[x]=tp;
        dfn[x]=++t;id[t]=x;
        if(wson[x])  dfs2(wson[x],tp);
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x]||y==wson[x])  continue;
            dfs2(y,y);
        }
        out[x]=t;
    }
} using namespace Tree_Chain_Partition;

namespace Segment_Tree{
    struct Tree{
        int l,r,sum,lazy;
        #define l(i) tr[i].l
        #define r(i) tr[i].r
        #define sum(i) tr[i].sum
        #define lazy(i) tr[i].lazy
        #define ls(i) (i<<1)
        #define rs(i) (i<<1|1)
    }tr[N<<2];
    void pushup(int i){sum(i)=sum(ls(i))+sum(rs(i));}
    void pushdown(int i){
        if(!lazy(i))  return;
        sum(ls(i))+=(r(ls(i))-l(ls(i))+1)*lazy(i);
        sum(rs(i))+=(r(rs(i))-l(rs(i))+1)*lazy(i);
        lazy(ls(i))+=lazy(i);
        lazy(rs(i))+=lazy(i);
        lazy(i)=0;
        return;
    }
    void build(int i,int l,int r){
        l(i)=l,r(i)=r;
        if(l==r){
            sum(i)=w[id[l]];
            return;
        }
        int mid=(l+r)>>1;
        build(ls(i),l,mid);build(rs(i),mid+1,r);
        pushup(i);
        return;
    }
    void modify(int i,int x,int k){
        int l=l(i),r=r(i);
        if(l==r){
            sum(i)+=k;
            return;
        }
        pushdown(i);
        int mid=(l+r)>>1;
        if(x<=mid)  modify(ls(i),x,k);
        else  modify(rs(i),x,k);
        pushup(i);
        return;
    }
    void update(int i,int ml,int mr,int k){
        int l=l(i),r=r(i);
        if(ml<=l&&r<=mr){
            lazy(i)+=k;
            sum(i)+=(r(i)-l(i)+1)*k;
            return;
        }
        pushdown(i);
        int mid=(l+r)>>1;
        if(ml<=mid)  update(ls(i),ml,mr,k);
        if(mid<mr)  update(rs(i),ml,mr,k);
        pushup(i);
        return;
    }
    int query(int i,int ql,int qr){
        int l=l(i),r=r(i);
        if(ql<=l&&r<=qr)  return sum(i);
        pushdown(i);
        int mid=(l+r)>>1,res=0;
        if(ql<=mid)  res+=query(ls(i),ql,qr);
        if(mid<qr)  res+=query(rs(i),ql,qr);
        pushup(i);
        return res;
    }

} using namespace Segment_Tree;


signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    n=read;m=read;
    for(int i=1;i<=n;i++)  w[i]=read;
    for(int i=1,a,b;i<n;i++)  a=read,b=read,add(a,b),add(b,a);
    dfs1(1);dfs2(1,1);
    build(1,1,t);
    int op,u,v,k,ans;
    while(m-->0){
        op=read;u=read;
        switch(op){
        case 1:
            k=read;
            modify(1,dfn[u],k);
            break;
        case 2:
            k=read;
            update(1,dfn[u],out[u],k);
            break;
        case 3:
            ans=0;
            while(u){
                ans+=query(1,dfn[top[u]],dfn[u]);
                u=fa[top[u]];
            }
            write(ans);pt;
            break;
        default:break;
        }
    }

    return 0;
}

\(T_C\) 难存的情缘

vjudge上没找着,挂学校OJ吧

板子,树剖,将边权下放到点上,然后单点修改区间查询最大值即可。
CODE

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
#define N 10010
int n,w[N];

namespace Tree_Chain_Partition{
    struct EDGE{int next,to,ww,number;}e[N<<1];
    int head[N],total;int dot[N];
    void add(int u,int v,int ww,int id){e[++total]={head[u],v,ww,id};head[u]=total;}

    int fa[N],depth[N],wson[N],siz[N];
    int top[N],dfn[N],id[N],t;
    void dfs1(int x){
        depth[x]=depth[fa[x]]+1;
        siz[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x])  continue;
            fa[y]=x;
            // cerr<<x<<"->"<<y<<'\n';
            // cerr<<"e["<<i<<"].num="<<e[i].number<<'\n';
            // cerr<<"e["<<i<<"].ww="<<e[i].ww<<'\n';
            dot[e[i].number]=y;
            w[y]=e[i].ww;
            dfs1(y);
            if(siz[y]>siz[wson[x]])  wson[x]=y;
            siz[x]+=siz[y];
        }
    }
    void dfs2(int x,int tp){
        top[x]=tp;
        dfn[x]=++t;id[t]=x;
        if(wson[x])  dfs2(wson[x],tp);
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x]||y==wson[x])  continue;
            dfs2(y,y);
        }
    }
} using namespace Tree_Chain_Partition;

namespace Segment_Tree{
    struct Tree{
        int l,r,mx;
        #define ls(i) (i<<1)
        #define rs(i) (i<<1|1)
        #define l(i) tr[i].l
        #define r(i) tr[i].r
        #define mx(i) tr[i].mx
    }tr[N<<2];
    void pushup(int i){mx(i)=max(mx(ls(i)),mx(rs(i)));}
    void build(int i,int l,int r){
        l(i)=l,r(i)=r;
        if(l==r){
            mx(i)=w[id[l]];
            return;
        }
        int mid=(l+r)>>1;
        build(ls(i),l,mid);build(rs(i),mid+1,r);
        pushup(i);
        return;
    }
    void modify(int i,int x,int k){
        int l=l(i),r=r(i);
        if(l==r){
            mx(i)=k;
            return;
        }
        int mid=(l+r)>>1;
        if(x<=mid)  modify(ls(i),x,k);
        else  modify(rs(i),x,k);
        pushup(i);
        return;
    }
    int query(int i,int ql,int qr){
        int l=l(i),r=r(i);
        if(ql<=l&&r<=qr){
            return mx(i);
        }
        int mid=(l+r)>>1,res=0;
        if(ql<=mid)  res=max(res,query(ls(i),ql,qr));
        if(mid<qr)  res=max(res,query(rs(i),ql,qr));
        return res;
    }
} using namespace Segment_Tree;


signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    n=read;
    for(int a,b,c,i=1;i<n;i++){
        a=read,b=read,c=read;
        add(a,b,c,i);add(b,a,c,i);
    }
    dfs1(1);dfs2(1,1);
    build(1,1,t);
    char op[7];
    int u,v,k,i,ans;
    while(1){
        scanf("%s",op);
        switch(op[0]){
        case 'C':
            i=read,k=read;
            u=dot[i];
            modify(1,dfn[u],k);
            break;
        case 'Q':
            u=read,v=read;ans=0;
            while(top[u]!=top[v]){
                if(depth[top[u]]<depth[top[v]])  swap(u,v);
                ans=max(ans,query(1,dfn[top[u]],dfn[u]));
                u=fa[top[u]];
            }
            ans=max(ans,(depth[u]<depth[v]?query(1,dfn[wson[u]],dfn[v]):query(1,dfn[wson[v]],dfn[u])));
            write(ans);pt;
            break;
        default:
            return 0;
        }
    }

    return 0;
}

\(T_D\) 货车运输

打 \(LCA\) 时打过一次,一样的思路,把倍增换成树剖。

首先一个 \(kruskal\) 重构树,然后就很板了,树剖,线段树维护区间最小值。
CODE

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define swap(x,y) (x^=y,y^=x,x^=y)
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
#define N 20010
#define M 50010
int n,m;
int w[N<<1];
namespace klskr{
    struct EDGE{int next,from,to,w;}e[M<<1],q[M<<1];
    int head[N],total,hq[N],tot;
    void add(int u,int v,int w){e[++total]={head[u],u,v,w};head[u]=total;}
    void addq(int u,int v){
        tot++;
        q[tot].next=hq[u];
        q[tot].to=v;
        hq[u]=tot;
    }
    int rt[N];
    int find(int x){return rt[x]==x?x:(rt[x]=find(rt[x]));}
} using namespace klskr;

namespace Tree_Chain_Partition{
    int fa[N],siz[N],depth[N],wson[N];
    int top[N],dfn[N],id[N],t;
    void dfs1(int x){
        depth[x]=depth[fa[x]]+1;
        siz[x]=1;
        for(int i=hq[x];i;i=q[i].next){
            int y=q[i].to;
            if(y==fa[x])  continue;
            fa[y]=x;
            dfs1(y);
            if(siz[y]>siz[wson[x]])  wson[x]=y;
            siz[x]+=siz[y];
        }
    }
    void dfs2(int x,int tp){
        top[x]=tp;
        dfn[x]=++t;id[t]=x;
        if(wson[x])  dfs2(wson[x],tp);
        for(int i=hq[x];i;i=q[i].next){
            int y=q[i].to;
            if(y==wson[x]||y==fa[x])  continue;
            dfs2(y,y);
        }
    }
} using namespace Tree_Chain_Partition;

namespace Segment_Tree{
    struct Tree{
        int l,r,minn;
        #define l(i) tr[i].l
        #define r(i) tr[i].r
        #define ls(i) (i<<1)
        #define rs(i) (i<<1|1)
        #define minn(i) tr[i].minn
    }tr[N<<2];
    void build(int i,int l,int r){
        l(i)=l,r(i)=r;
        if(l==r){
            minn(i)=w[id[l]];
            return;
        }
        int mid=(l+r)>>1;
        build(ls(i),l,mid);
        build(rs(i),mid+1,r);
        minn(i)=min(minn(ls(i)),minn(rs(i)));
        return;
    }
    int query(int i,int ql,int qr){
        int l=l(i),r=r(i);
        if(ql<=l&&r<=qr)  return minn(i);
        int mid=(l+r)>>1,res=1e8;
        if(ql<=mid)  res=min(res,query(ls(i),ql,qr));
        if(mid<qr)  res=min(res,query(rs(i),ql,qr));
        return res;
    }
} using namespace Segment_Tree;


signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    n=read,m=read;
    for(int i=1;i<=(n<<1);i++)  rt[i]=i,w[i]=1e8;
    for(int i=1,a,b,c;i<=m;i++)  a=read,b=read,c=read,add(a,b,c),add(b,a,c);
    sort(e+1,e+total+1,[](EDGE a,EDGE b){return a.w>b.w;});
    for(int i=1;i<=total;i++){
        EDGE x=e[i];
        int u=x.from,v=x.to;
        int uu=find(u),vv=find(v);
        if(uu!=vv){
            rt[uu]=rt[vv]=++n;
            addq(n,uu);addq(uu,n);
            addq(n,vv),addq(vv,n);
            w[n]=x.w;
        }
    }
    for(int i=n;i>=1;i--){
        if(!dfn[i]){
            dfs1(i);
            dfs2(i,i);
        }
    }
    build(1,1,t);
    int T,u,v,ans;
    T=read;
    while(T-->0){
        u=read,v=read;
        int uu=find(u),vv=find(v);
        if(uu!=vv){
            puts("-1");
            continue;
        }
        ans=1e8;
        while(top[u]!=top[v]){
            if(depth[top[u]]<depth[top[v]])  swap(u,v);
            ans=min(ans,query(1,dfn[top[u]],dfn[u]));
            u=fa[top[u]];
        }
        ans=min(ans,(depth[u]<depth[v] ? query(1,dfn[u],dfn[v]) : query(1,dfn[v],dfn[u])));
        write(ans);pt;
    }

    return 0;
}

\(T_E\) 遥远的国度

涉及换根,那就换,我们"假装"换,(用一个变量 \(rt\) 存储当前的根,直接换),但实际根仍为 \(1\),树剖,线段树维护单点修改和区间最小值,单点修改不变,对于区间最小值:

  1. \(rt=u\) 查整棵树。
  2. \(rt\) 不在 \(u\) 子树里,该查啥查啥。
  3. \(rt\) 在 \(u\) 子树里,考虑将查询区间分成两段,设 \(v\) 为 \(u\) 在 \(rt\) 方向上的儿子,答案显然是总答案减去去 \(v\) 的子树的贡献,即分成 \([1,dfn[v]-1]\) 和 \([out[v]+1,t]\) 两段答案进行统计即可。

对于换根我们的大体思路就是这样。
CODE

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define swap(x,y) (x^=y,y^=x,x^=y)
#define int long long
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
#define N 100010
const int MAX=1e10;
int n,m,rt;
int w[N];
struct EDGE{int next,to;}e[N<<1];
int head[N],total;
void add(int u,int v){ e[++total]={head[u],v}; head[u]=total;}

namespace Tree_Chain_Partition{
    int fa[N],siz[N],wson[N],depth[N];
    int dfn[N],out[N],id[N],t,top[N];
    void dfs(int x){
        depth[x]=depth[fa[x]]+1;
        siz[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x])  continue;
            fa[y]=x;
            dfs(y);
            if(siz[y]>siz[wson[x]])  wson[x]=y;
            siz[x]+=siz[y];
        }
    }
    void dfs(int x,int tp){
        top[x]=tp;
        dfn[x]=++t;id[t]=x;
        if(wson[x])  dfs(wson[x],tp);
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x]||y==wson[x])  continue;
            dfs(y,y);
        }
        out[x]=t;
    }
} using namespace Tree_Chain_Partition;

namespace Segment_Tree{
    struct Tree{
        int l,r,minn,lazy;
        #define l(i) tr[i].l
        #define r(i) tr[i].r
        #define minn(i) tr[i].minn
        #define lazy(i) tr[i].lazy
        #define ls(i) (i<<1)
        #define rs(i) (i<<1|1)
    }tr[N<<2];
    void pushup(int i){minn(i)=min(minn(ls(i)),minn(rs(i)));}
    void pushdown(int i){
        if(!lazy(i)) return;
        minn(ls(i))=minn(rs(i))=lazy(ls(i))=lazy(rs(i))=lazy(i);
        lazy(i)=0;
        return;
    }
    void build(int i,int l,int r){
        l(i)=l,r(i)=r;
        if(l==r){
            minn(i)=w[id[l]];
            return;
        }
        int mid=(l+r)>>1;
        build(ls(i),l,mid);
        build(rs(i),mid+1,r);
        pushup(i);
        return;
    }
    void update(int i,int ul,int ur,int k){
        int l=l(i),r=r(i);
        if(ul<=l&&r<=ur){
            minn(i)=lazy(i)=k;
            return;
        }
        pushdown(i);
        int mid=(l+r)>>1;
        if(ul<=mid)  update(ls(i),ul,ur,k);
        if(mid<ur)  update(rs(i),ul,ur,k);
        pushup(i);
        return;
    }
    int query(int i,int ql,int qr){
        int l=l(i),r=r(i);
        if(ql<=l&&r<=qr){
            return minn(i);
        }
        pushdown(i);
        int mid=(l+r)>>1,res=MAX;
        if(ql<=mid)  res=min(res,query(ls(i),ql,qr));
        if(mid<qr)  res=min(res,query(rs(i),ql,qr));
        pushup(i);
        return res;
    }
} using namespace Segment_Tree;

signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    n=read,m=read;
    for(int a,b,i=1;i<n;i++){
        a=read,b=read;
        add(a,b),add(b,a);
    }
    for(int i=1;i<=n;i++)  w[i]=read;
    dfs(1);dfs(1,1);
    build(1,1,t);
    rt=read;
    int op,u,v,k,ans;
    while(m-->0){
        op=read;
        switch(op){
        case 1:
            rt=read;
            break;
        case 2:
            u=read,v=read,k=read;
            while(top[u]!=top[v]){
                if(depth[top[u]]<depth[top[v]])  swap(u,v);
                update(1,dfn[top[u]],dfn[u],k);
                u=fa[top[u]];
            }
            depth[u]<depth[v] ? update(1,dfn[u],dfn[v],k) : update(1,dfn[v],dfn[u],k);
            break;
        case 3:
            u=read;ans=MAX;
            if(u==rt)  ans=minn(1);
            else if(dfn[u]<dfn[rt]&&dfn[rt]<=out[u]){
                v=rt;
                while(top[u]!=top[v]){
                    if(fa[top[v]]==u){
                        v=top[v];break;
                    }
                    v=fa[top[v]];
                }
                if(fa[v]!=u)  v=wson[u];
                if(dfn[v]-1>=1)  ans=min(ans,query(1,1,dfn[v]-1));
                if(out[v]+1<=t)  ans=min(ans,query(1,out[v]+1,t));
            }
            else
                ans=query(1,dfn[u],out[u]);
            write(ans);pt;
            break;
        default:
            break;
        }
    }
    return 0;
}

对于换根,还可以求 \(LCA\),比如 Jamie and Tree,我们可以分讨归纳证明,节点 \(u,v\) 在根为 \(rt\) 时的 \(LCA\) 为 \(lca(u,v),lca(u,rt),lca(v,rt)\) 中深度最大的节点,其他的按照上面的思路维护即可,注意修改和查询都要分讨。
CODE

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
const int N=1e5+10;
int n,q;
int w[N];
struct EDGE{int next,to;}e[N<<1];
int head[N],total,rt=1;
void add(int u,int v){e[++total]={head[u],v};head[u]=total;}

namespace Tree_Chain_Partition{
    int fa[N],depth[N],siz[N],wson[N];
    int top[N],dfn[N],out[N],id[N],t;
    void dfs(int x){
        depth[x]=depth[fa[x]]+1;
        siz[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x])  continue;
            fa[y]=x;
            dfs(y);
            if(siz[y]>siz[wson[x]])  wson[x]=y;
            siz[x]+=siz[y];
        }
    }
    void dfs(int x,int tp){
        top[x]=tp;
        dfn[x]=++t;id[t]=x;
        if(wson[x])  dfs(wson[x],tp);
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x]||y==wson[x])  continue;
            dfs(y,y);
        }
        out[x]=t;
    }
    int LCA(int u,int v){
        while(top[u]!=top[v]){
            if(depth[top[u]]<depth[top[v]])  swap(u,v);
            u=fa[top[u]];
        }
        return (depth[u]<depth[v] ? u : v);
    }
    int findson(int u,int rt){//查询u在rt方向上的儿子
        int v=rt;
        while(top[v]!=top[u]){
            if(fa[top[v]]==u){
                v=top[v];break;
            }
            v=fa[top[v]];
        }
        if(fa[v]!=u)  v=wson[u];
        return v;
    }
} using namespace Tree_Chain_Partition;

namespace Segment_Tree{
    struct Tree{
        int l,r,sum,lazy;
        #define l(i) tr[i].l
        #define r(i) tr[i].r
        #define sum(i) tr[i].sum
        #define lazy(i) tr[i].lazy
        #define ls(i) (i<<1)
        #define rs(i) (i<<1|1)
    }tr[N<<2];
    void pushup(int i){sum(i)=sum(ls(i))+sum(rs(i));}
    void pushdown(int i){
        if(!lazy(i)) return;
        sum(ls(i))+=(r(ls(i))-l(ls(i))+1)*lazy(i);
        sum(rs(i))+=(r(rs(i))-l(rs(i))+1)*lazy(i);
        lazy(ls(i))+=lazy(i),lazy(rs(i))+=lazy(i);
        lazy(i)=0;
        return;
    }
    void build(int i,int l,int r){
        l(i)=l,r(i)=r;
        if(l==r){
            sum(i)=w[id[l]];
            return;
        }
        int mid=(l+r)>>1;
        build(ls(i),l,mid);
        build(rs(i),mid+1,r);
        pushup(i);
        return;
    }
    void update(int i,int ul,int ur,int k){
        int l=l(i),r=r(i);
        if(ul<=l&&r<=ur){
            lazy(i)+=k;
            sum(i)+=(r-l+1)*k;
            return;
        }
        pushdown(i);
        int mid=(l+r)>>1;
        if(ul<=mid)  update(ls(i),ul,ur,k);
        if(mid<ur)  update(rs(i),ul,ur,k);
        pushup(i);
        return;
    }
    int query(int i,int ql,int qr){
        int l=l(i),r=r(i);
        if(ql<=l&&r<=qr){
            return sum(i);
        }
        pushdown(i);
        int mid=(l+r)>>1,res=0;
        if(ql<=mid)  res+=query(ls(i),ql,qr);
        if(mid<qr)  res+=query(rs(i),ql,qr);
        pushup(i);
        return res;
    }
} using namespace Segment_Tree;

signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    n=read,q=read;
    for(int i=1;i<=n;i++)  w[i]=read;
    for(int a,b,i=1;i<n;i++){
        a=read,b=read;add(a,b),add(b,a);
    }
    dfs(1);dfs(1,1);
    build(1,1,t);
    int op,u,v,k,ans;
    int lca,lca1,lca2,lca3;
    while(q-->0){
        op=read;
        switch(op){
            case 1:
                rt=read;
                break;
            case 2:
                u=read,v=read,k=read;
                lca1=LCA(u,v);
                lca2=LCA(u,rt);
                lca3=LCA(v,rt);
                lca=(depth[lca1]>depth[lca2] ? lca1 : lca2);
                lca=(depth[lca]>depth[lca3] ? lca : lca3);
                if(lca==rt)
                    update(1,1,t,k);
                
                else if(dfn[lca]<dfn[rt]&&dfn[rt]<=out[lca]){
                    v=findson(lca,rt);
                    if(dfn[v]-1>=1)  update(1,1,dfn[v]-1,k);
                    if(out[v]+1<=t)  update(1,out[v]+1,t,k);
                }
                else
                    update(1,dfn[lca],out[lca],k);
                break;
            case 3:
                u=read;
                ans=0;
                if(u==rt){
                    ans=query(1,1,t);
                }
                else if(dfn[u]<dfn[rt]&&dfn[rt]<=out[u]){
                    v=findson(u,rt);
                    if(dfn[v]-1>=1)  ans+=query(1,1,dfn[v]-1);
                    if(out[v]+1<=t)  ans+=query(1,out[v]+1,t);
                }
                else
                    ans=query(1,dfn[u],out[u]);
                write(ans),pt;
                break;
            default:break;
        }
    }

    return 0;
}

\(T_F\) 染色

思路差不多,多了点细节,线段树每个节点多维护一个 \(lc\) 和一个 \(rc\) 为每段左右端点的颜色,然后注意查完一段 \([dfn[top[u]],dfn[u]]\) 后,特判树链交界处,查一下 \(top[u]\) 和 \(fa[top[u]]\) 的颜色是否相同,相同就要让答案减1。

我纯唐,调了一下午分块,觉得分块好维护,寄。
CODE

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define swap(x,y) (x^=y,y^=x,x^=y)
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
const int N=1e5+10;
#define M 400
int n,m;
int c[N];
struct EDGE{int next,to;}e[N<<1];
int head[N],total;
void add(int u,int v){e[++total]={head[u],v};head[u]=total;}

namespace Tree_Chain_Partition{
    int fa[N],siz[N],depth[N],wson[N];
    int top[N],dfn[N],id[N],t;
    void dfs(int x){
        depth[x]=depth[fa[x]]+1;
        siz[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x])  continue;
            fa[y]=x;
            dfs(y);
            if(siz[y]>siz[wson[x]])  wson[x]=y;
            siz[x]+=siz[y];
        }
    }
    void dfs(int x,int tp){
        top[x]=tp;
        dfn[x]=++t;id[t]=x;
        if(wson[x])  dfs(wson[x],tp);
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x]||y==wson[x])  continue;
            dfs(y,y);
        }
    }
} using namespace Tree_Chain_Partition;

namespace Segment_Tree{
    struct Tree{
        int l,r,cnt,lc,rc,lazy;
        #define l(i) tr[i].l
        #define r(i) tr[i].r
        #define cnt(i) tr[i].cnt
        #define lc(i) tr[i].lc
        #define rc(i) tr[i].rc
        #define lazy(i) tr[i].lazy
        #define ls(i) (i<<1)
        #define rs(i) (i<<1|1)
    }tr[N<<2];
    void pushup(int i){
        lc(i)=lc(ls(i)),rc(i)=rc(rs(i));
        cnt(i)=cnt(ls(i))+cnt(rs(i));
        if(rc(ls(i))==lc(rs(i)))  --cnt(i);
        return;
    }
    void pushdown(int i){
        if(!lazy(i))  return;
        lazy(ls(i))=lazy(rs(i))=lazy(i);
        lc(ls(i))=rc(ls(i))=lazy(i);
        lc(rs(i))=rc(rs(i))=lazy(i);
        cnt(ls(i))=cnt(rs(i))=1;
        lazy(i)=0;
        return;
    }
    void build(int i,int l,int r){
        l(i)=l,r(i)=r;
        if(l==r){
            lc(i)=rc(i)=c[id[l]];
            cnt(i)=1;
            return;
        }
        int mid=(l+r)>>1;
        build(ls(i),l,mid);
        build(rs(i),mid+1,r);
        pushup(i);
        return;
    }
    void update(int i,int ul,int ur,int k){
        int l=l(i),r=r(i);
        if(ul<=l&&r<=ur){
            lazy(i)=k;
            lc(i)=rc(i)=k;
            cnt(i)=1;
            return;
        }
        pushdown(i);
        int mid=(l+r)>>1;
        if(ul<=mid)  update(ls(i),ul,ur,k);
        if(mid<ur)  update(rs(i),ul,ur,k);
        pushup(i);
        return;
    }
    int ask(int i,int x){
        int l=l(i),r=r(i);
        if(l==r)  return lc(i);
        pushdown(i);
        int mid=(l+r)>>1,res;
        if(x<=mid)  res=ask(ls(i),x);
        else  res=ask(rs(i),x);
        pushup(i);
        return res;
    }
    int query(int i,int ql,int qr){
        int l=l(i),r=r(i);
        if(ql<=l&&r<=qr){
            return cnt(i);
        }
        pushdown(i);
        int mid=(l+r)>>1,res=0;
        if(ql<=mid&&mid<qr){
            if(rc(ls(i))==lc(rs(i)))  res--;
        }
        if(ql<=mid)  res+=query(ls(i),ql,qr);
        if(mid<qr)  res+=query(rs(i),ql,qr);
        pushup(i);
        return res;
    }
} using namespace Segment_Tree;

signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    n=read,m=read;
    for(int i=1;i<=n;i++)  c[i]=read;
    for(int a,b,i=1;i<n;i++){
        a=read,b=read;add(a,b),add(b,a);
    }
    dfs(1),dfs(1,1);
    build(1,1,t);
    char op;int u,v,k,ans;
    while(m-->0){
        op=getchar();while(op!='Q'&&op!='C') op=getchar();
        u=read,v=read;
        if(op=='Q'){
            ans=0;
            while(top[u]!=top[v]){
                if(depth[top[u]]<depth[top[v]])  swap(u,v);
                ans+=query(1,dfn[top[u]],dfn[u]);
                u=top[u];
                int a=ask(1,dfn[u]),b=ask(1,dfn[fa[u]]);
                if(a==b)  ans--;
                u=fa[u];
            }
            ans+=(depth[u]<depth[v] ? query(1,dfn[u],dfn[v]) : query(1,dfn[v],dfn[u]));
            write(ans);pt;
        }
        if(op=='C'){
            k=read;
            while(top[u]!=top[v]){
                if(depth[top[u]]<depth[top[v]])  swap(u,v);
                update(1,dfn[top[u]],dfn[u],k);
                u=fa[top[u]];
            }
            depth[u]<depth[v] ? update(1,dfn[u],dfn[v],k) : update(1,dfn[v],dfn[u],k);
        }
    }

    return 0;
}

\(T_G\) 运输计划

二分答案树上差分,几个月前打 \(LCA\) 时就打了,当时倍增就被卡了,树剖跑飞快。

$$ my blog $$
CODE

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define swap(x,y) (x^=y,y^=x,x^=y)
#define read read()
#define pt puts("")
inline int read{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x){
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
#define N 300010
int n,m;
int MAX;
struct EDGE{int next,to,t;}e[N<<1];
int head[N],total;
void add(int u,int v,int t){e[++total]={head[u],v,t};head[u]=total;}
int edge[N];
namespace Tree_Chain_Partition{
    int fa[N],siz[N],wson[N],depth[N];
    int top[N];
    int dis[N];
    void dfs1(int x){
        depth[x]=depth[fa[x]]+1;
        siz[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==fa[x])  continue;
            edge[y]=i;
            dis[y]=dis[x]+e[i].t;
            fa[y]=x;
            dfs1(y);
            if(siz[y]>siz[wson[x]])  wson[x]=y;
            siz[x]+=siz[y];
        }
    }
    void dfs2(int x,int tp){
        top[x]=tp;
        if(wson[x])  dfs2(wson[x],tp);
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(y==wson[x]||y==fa[x])  continue;
            dfs2(y,y);
        }
        
    }
    int LCA(int u,int v){
        while(top[u]!=top[v]){
            if(depth[top[u]]<depth[top[v]])  swap(u,v);
            u=fa[top[u]];
        }
        return (depth[u]<depth[v]?u:v);
    }
} using namespace Tree_Chain_Partition;
int u[N],v[N],lca[N],len[N];
int b[N];
int sum,reduce;
void dfs(int x){
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].to;
        if(y==fa[x])  continue;
        dfs(y);
        b[x]+=b[y];
    }
    if(b[x]==sum)  reduce=max(reduce,e[edge[x]].t);
}
bool check(int x){
    for(int i=1;i<=n;i++)  b[i]=0;
    sum=reduce=0;
    for(int i=1;i<=m;i++){
        if(len[i]>x){
            sum++;
            b[u[i]]++;
            b[v[i]]++;
            b[lca[i]]-=2;
        }
    }
    dfs(1);
    if(MAX-reduce<=x)  return 1;
    return 0;
}

int ans;
signed main()
{
    #ifndef ONLINE_JUDGE
        freopen("lty.in","r",stdin);
        freopen("lty.out","w",stdout);
    #endif
    n=read,m=read;
    for(int a,b,c,i=1;i<n;i++){
        a=read,b=read,c=read;
        add(a,b,c),add(b,a,c);
    }
    dfs1(1),dfs2(1,1);
    for(int i=1;i<=m;i++){
        u[i]=read,v[i]=read;
        lca[i]=LCA(u[i],v[i]);
        len[i]=dis[u[i]]+dis[v[i]]-2*dis[lca[i]];
        MAX=max(MAX,len[i]);
    }
    int st=0,ed=MAX;
    while(st<ed){
        int mid=(st+ed)>>1;
        if(check(mid))
            ed=ans=mid;
        else
            st=mid+1;
    }
    write(ans);
    return 0;
}
相关推荐
qwq_ovo_pwp1 个月前
题解 洛谷 Luogu P2440 木材加工 二分答案 C/C++
c语言·c++·算法·二分答案·洛谷
hnjzsyjyj6 个月前
DFS序 & 欧拉序
图论·lca·dfs序·欧拉序
lty_ylzsx7 个月前
平衡树 Treap & Splay [学习笔记]
线段树·字符串·dp·二分答案·splay·fhq_treap·treap
RoysterCDD9 个月前
【C++算法】洛谷P1102:A-B数对,思路,lower_bound,upper_bound,二分答案,代码详解
c++·算法·stl·二分·二分答案·洛谷·算法竞赛
卡布叻_周深10 个月前
2024初三年后集训模拟测试3
dp·树形dp·二分答案·单调队列优化dp
wang_nn1 年前
【每日一题】逃离火灾
bfs·数组·二分答案·2023-11-09
小威W1 年前
【算法】树上倍增 & LCA
算法···lca·倍增·树上倍增·最近公共祖先
是基德吖1 年前
【洛谷】P2678 跳石头
c++·算法·二分·二分答案·洛谷
陈进士学习1 年前
AcWing1171. 距离(lca&&tarjan)
算法·深度优先·图论·tarjan算法·lca