【模板】树链剖分

树链剖分在算法竞赛之中的运用和线段树一般灵活,所以说没有什么固定化的模板,只能以某些功能为例写模板。
这里就写出能够快速维护下面一些操作的树链剖分模板:
将树从x到y结点最短路径上所有节点的值都加上z
求树从x到y结点最短路径上所有节点的值之和

这里首先给出的不是普通的静态数组树链剖分,而是链表版本的树链剖分(连线段树也是链表版本的),假如已经做好准备,就继续看下去吧。

先说一声,这篇博文就是orz了大佬博客后写的,大体风格差不多
这里写图片描述
在这幅图中,用蓝线描好的是重边,重边连起来形成重链,其余的都是轻边。
先上了一堆树链剖分的专用术语,现在要对它们解释一下了。

重儿子:一个非叶子结点的各个儿子中拥有最大子树的儿子
重边:一个非叶子结点与它的重儿子相连的边
轻边:概念与重边相对,非叶子结点与轻儿子相连的边
重链:若干条重边首尾相连形成重链

这种剖分方法是有理由的。

首先,对于轻边(U,V),size(V)<=size(U)/2。很好证,瞪眼法即可。
第二,从根到某一点的路径上,不超过O(logN)条轻边,不超过O(logN)条重链。

第二个性质很重要,这直接决定了树链剖分的 O(nlogn) O ( n l o g n ) 的高效的时间复杂度。


结构体s

struct Node {      //结点信息
    struct Edge *head;    //前向星存边
    struct Chain *chain;  //所存链的信息
    Node *father, *son;   //父亲节点和儿子结点
    int size, dfn, depth, value; //size表示以该节点为根的子树中结点数
                                //dfn记录DFS序,线段树维护
                                //depth记录结点深度
                                //value记录权值
    bool visit;                 //visit记录是否访问,DFS中运用
}info[MAXN];
struct Edge {      //前向星存边信息
    Node *from, *to;
    Edge *next;
    Edge(Node *from, Node *to) : from(from), to(to), next(from->head) {}   //构造函数
};
struct Chain {    //重链信息
    Node *top;    //链顶信息
    Chain(Node *top) : top(top) {}
};

加边

inline void AddEdge(int from, int to) {
    info[from].head = new Edge(&info[from], &info[to]);
    info[to].head = new Edge(&info[to], &info[from]);
}

做法

树链剖分分为两步(后续还要线段树维护信息),由两遍DFS完成。

第一遍DFS 找重边
void dfs1(Node *u) {   //当前结点u
    u->visit = true;   //标记已访问
    u->size = 1;       //首先更新u为根子树结点数为1(后面统计)
    for (Edge *e = u->head; e; e = e->next) {
        Node *v = e->to;
        if (!v->visit) {   
            v->father = u;  
            v->depth = u->depth + 1;
            dfs1(v);
            u->size += v->size;   //u的size更新,加上子节点的size
            if (!u->son || u->son->size < v->size) 
                u->son = v;   //判断v是否是u的重儿子
        }
    }
}
第二遍DFS 连重边成重链
void dfs2(Node *u) {
    static int dfs_clock = 0;  //时间戳
    u->dfn = ++dfs_clock;  //敲一个时间戳
    if (!u->father || u != u->father->son)   //如果重链断了重开一条
        u->chain = new Chain(u);
    else u->chain = u->father->chain; //否则就接上
    if (u->son) dfs2(u->son); //重儿子DFS
    for (Edge *e = u->head; e; e = e->next) { 
        Node *v = e->to;
        if (v->father == u && v != u->son) 
            dfs2(v); //轻儿子DFS
    }
}
split
inline void split(int root) {
    info[root].depth = 1;
    dfs1(&info[root]);   //由于DFS调用指针,接口输入要取地址
    dfs2(&info[root]);   
}

线段树部分

可以参考
具体不解释,value在之后逐个加入,所以构造函数只有一个

struct SegTree {  
    int l, r;  
    SegTree *lc, *rc;  
    long long sum, add;  
    SegTree(int l, int r, SegTree *lc, SegTree *rc)  
        : l(l), r(r), lc(lc), rc(rc), sum(0), add(0) {}  
    void cover(long long rhs) {  
        add += rhs;  
        sum += rhs * (r - l + 1);   
    }  
    void pushup() {  
        sum = lc->sum + rc->sum;   
    }  
    void pushdown() {  
        lc->cover(add);  
        rc->cover(add);  
        add = 0;  
    }  
    void update(int l, int r, long long rhs) {  
        if (l > this->r || r < this->l) return;  
        else if (l <= this->l && this->r <= r) cover(rhs);  
        else {  
            pushdown();  
            lc->update(l, r, rhs);  
            rc->update(l, r, rhs);  
            pushup();  
        }  
        return;  
    }  
    long long query(int l, int r) {  
        if (l > this->r || r < this->l) return 0;  
        if (l <= this->l && this->r <= r) return sum;  
        pushdown();  
        return lc->query(l, r) + rc->query(l, r);  
    }  
    static SegTree *build(int l, int r) {  
        if (l > r) return NULL;  
        else if (l == r) return new SegTree(l, r, NULL, NULL);  
        else {  
            int mid = (l + r) >> 1;  
            return new SegTree(l, r, build(l, mid), build(mid + 1, r));  
        }  
    }  
}*root;  

树链剖分更新部分

本来要对每条重链开一棵线段树,为了方便,我们只开一棵线段树。所以在输入之后无法直接在线段树上面进行修改。由于只能让x和y在同一条重链上才能进行方便的修改,所以要让两个结点跳到同一条重链上

void update(int x, int y, int z) {  //x和y的路径上的所有点的点权增加z
    Node *u = &info[x], *v = &info[y];  //找到指向x和y的指针
    while (u->chain != v->chain) {  //如果u和v不在同一条重链上
        if (u->chain->top->depth < v->chain->top->depth) std::swap(u, v); //让u所在的重链深度更深
        root->update(u->chain->top->dfn, u->dfn, z); //修改u所在重链上的信息
        u = u->chain->top->father; //u跳到上一条重链上
    }
    if (u->depth > v->depth) std::swap(u, v); //让u深度更浅
    root->update(u->dfn, v->dfn, z); //由于u和v已经在同一条重链上,直接修改
}
树链剖分查询部分

与updata类似,只有当两个结点在同一条重链上才可以直接查询,所以基本操作还是跳。

long long query(int x, int y) {
    Node *u = &info[x], *v = &info[y];
    long long result = 0;
    while (u->chain != v->chain) { //跳的过程
        if (u->chain->top->depth < v->chain->top->depth) std::swap(u, v);
        result += root->query(u->chain->top->dfn, u->dfn); //查询累加
        u = u->chain->top->father;
    }
    if (u->depth > v->depth) std::swap(u, v);
    result += root->query(u->dfn, v->dfn); //查询
    return result;
}

完整代码

struct Node {
    struct Edge *head;
    struct Chain *chain;
    Node *father, *son;
    int size, dfn, depth, value;
    bool visit;
}info[MAXN];
struct Edge {
    Node *from, *to;
    Edge *next;
    Edge(Node *from, Node *to) : from(from), to(to), next(from->head) {}
};
struct Chain {
    Node *top;
    Chain(Node *top) : top(top) {}
};
inline void AddEdge(int from, int to) {
    info[from].head = new Edge(&info[from], &info[to]);
    info[to].head = new Edge(&info[to], &info[from]);
}
void dfs1(Node *u) {
    u->visit = true;
    u->size = 1;
    for (Edge *e = u->head; e; e = e->next) {
        Node *v = e->to;
        if (!v->visit) {
            v->father = u;
            v->depth = u->depth + 1;
            dfs1(v);
            u->size += v->size;
            if (!u->son || u->son->size < v->size) 
                u->son = v;
        }
    }
}
void dfs2(Node *u) {
    static int dfs_clock = 0;
    u->dfn = ++dfs_clock;
    if (!u->father || u != u->father->son) 
        u->chain = new Chain(u);
    else u->chain = u->father->chain;
    if (u->son) dfs2(u->son);
    for (Edge *e = u->head; e; e = e->next) { 
        Node *v = e->to;
        if (v->father == u && v != u->son) 
            dfs2(v);
    }
}
inline void split(int root) {
    info[root].depth = 1;
    dfs1(&info[root]);
    dfs2(&info[root]);
}
struct SegTree {  
    int l, r;  
    SegTree *lc, *rc;  
    long long sum, add;  
    SegTree(int l, int r, SegTree *lc, SegTree *rc)  
        : l(l), r(r), lc(lc), rc(rc), sum(0), add(0) {}  
    void cover(long long rhs) {  
        add += rhs;  
        sum += rhs * (r - l + 1);   
    }  
    void pushup() {  
        sum = lc->sum + rc->sum;   
    }  
    void pushdown() {  
        lc->cover(add);  
        rc->cover(add);  
        add = 0;  
    }  
    void update(int l, int r, long long rhs) {  
        if (l > this->r || r < this->l) return;  
        else if (l <= this->l && this->r <= r) cover(rhs);  
        else {  
            pushdown();  
            lc->update(l, r, rhs);  
            rc->update(l, r, rhs);  
            pushup();  
        }  
        return;  
    }  
    long long query(int l, int r) {  
        if (l > this->r || r < this->l) return 0;  
        if (l <= this->l && this->r <= r) return sum;  
        pushdown();  
        return lc->query(l, r) + rc->query(l, r);  
    }  
    static SegTree *build(int l, int r) {  
        if (l > r) return NULL;  
        else if (l == r) return new SegTree(l, r, NULL, NULL);  
        else {  
            int mid = (l + r) >> 1;  
            return new SegTree(l, r, build(l, mid), build(mid + 1, r));  
        }  
    }  
}*root;  
void update(int x, int y, int z) {
    Node *u = &info[x], *v = &info[y];
    while (u->chain != v->chain) {
        if (u->chain->top->depth < v->chain->top->depth) std::swap(u, v);
        root->update(u->chain->top->dfn, u->dfn, z);
        u = u->chain->top->father;
    }
    if (u->depth > v->depth) std::swap(u, v);
    root->update(u->dfn, v->dfn, z);
}
long long query(int x, int y) {
    Node *u = &info[x], *v = &info[y];
    long long result = 0;
    while (u->chain != v->chain) {
        if (u->chain->top->depth < v->chain->top->depth) std::swap(u, v);
        result += root->query(u->chain->top->dfn, u->dfn);
        u = u->chain->top->father;
    }
    if (u->depth > v->depth) std::swap(u, v);
    result += root->query(u->dfn, v->dfn);
    return result;
}
#include <cstdio> #include <iostream> #include <vector> #define N 30003 #define INF 2147483647 using namespace std; int n,f[N][20],dep[N],siz[N],son[N],top[N],tot,pos[N],w[N]; int Max[N*4],Sum[N*4]; vector <int> to[N]; void dfs1(int x){ siz[x]=1; int sz=to[x].size(); for(int i=0;i<sz;++i){ int y=to[x][i]; if(y==f[x][0])continue; f[y][0]=x; dep[y]=dep[x]+1; dfs1(y); siz[x]+=siz[y]; if(siz[y]>siz[son[x]])son[x]=y; } } void dfs2(int x,int root){ top[x]=root; pos[x]=++tot; if(son[x])dfs2(son[x],root); int sz=to[x].size(); for(int i=0;i<sz;++i){ int y=to[x][i]; if(y==f[x][0] || y==son[x])continue; dfs2(y,y); } } void update(int k,int l,int r,int P,int V){ if(l==r){ Max[k]=Sum[k]=V; return; } int mid=(l+r)>>1; if(P<=mid)update(k*2,l,mid,P,V); else update(k*2+1,mid+1,r,P,V); Max[k]=max(Max[k*2],Max[k*2+1]); Sum[k]=Sum[k*2]+Sum[k*2+1]; } void up(int &x,int goal){ for(int i=15;i>=0;--i) if(dep[f[x][i]]>=goal)x=f[x][i]; } int lca(int x,int y){ if(dep[x]>dep[y])up(x,dep[y]); if(dep[x]<dep[y])up(y,dep[x]); if(x==y)return x; for(int i=15;i>=0;--i) if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i]; return f[x][0]; } int getm(int k,int l,int r,int L,int R){ if(L<=l && r<=R)return Max[k]; int res=-INF,mid=(l+r)>>1; if(L<=mid)res=max(res,getm(k*2,l,mid,L,R)); if(R>mid)res=max(res,getm(k*2+1,mid+1,r,L,R)); return res; } int gets(int k,int l,int r,int L,int R){ if(L<=l && r<=R)return Sum[k]; int res=0,mid=(l+r)>>1; if(L<=mid)res+=gets(k*2,l,mid,L,R); if(R>mid)res+=gets(k*2+1,mid+1,r,L,R); return res; } int main(){ scanf("%d",&n); for(int i=1,a,b;i<n;++i){ scanf("%d%d",&a,&b); to[a].push_back(b); to[b].push_back(a); } dep[1]=1; dfs1(1); dfs2(1,1); for(int i=1;i<=15;++i) for(int j=1;j<=n;++j)f[j][i]=f[f[j][i-1]][i-1]; for(int i=1;i<=n;++i){ scanf("%d",&w[i]); update(1,1,n,pos[i],w[i]); } int q; scanf("%d",&q); while(q--){ char s[10]; int u,v,t; scanf("%s",s); if(s[1]=='H'){ scanf("%d%d",&u,&t); w[u]=t; update(1,1,n,pos[u],t); } if(s[1]=='M'){ scanf("%d%d",&u,&v); int ans=-INF,t=lca(u,v); for(int i=u;i;i=f[top[i]][0]) if(dep[t]<dep[top[i]]) ans=max(ans,getm(1,1,n,pos[top[i]],pos[i])); else{ ans=max(ans,getm(1,1,n,pos[t],pos[i])); break; } for(int i=v;i;i=f[top[i]][0]) if(dep[t]<dep[top[i]]) ans=max(ans,getm(1,1,n,pos[top[i]],pos[i])); else{ ans=max(ans,getm(1,1,n,pos[t],pos[i])); break; } printf("%d\n",ans); } if(s[1]=='S'){ scanf("%d%d",&u,&v); int ans=0,t=lca(u,v); for(int i=u;i;i=f[top[i]][0]) if(dep[t]<dep[top[i]]) ans+=gets(1,1,n,pos[top[i]],pos[i]); else{ ans+=gets(1,1,n,pos[t],pos[i]); break; } for(int i=v;i;i=f[top[i]][0]) if(dep[t]<dep[top[i]]) ans+=gets(1,1,n,pos[top[i]],pos[i]); else{ ans+=gets(1,1,n,pos[t],pos[i]); break; } printf("%d\n",ans-w[t]); } } }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值