树点分治与树链刨分

2 篇文章 0 订阅
1 篇文章 0 订阅

树分治:

树分治是一种解决树上路径问题的一种神奇方法.
它可以把复杂度为 O(nklogn...nk+1) 问题变为最高复杂度为 O(nklogn) 的问题(当然不是随便乱搞的)

假如,一颗树长得很正常,那么它的深度大约为 logn .
但是,如果一棵树长得很畸形(比如一条链),那深度就是n了.

如果对于每一个子树,可以随便选择根节点的话,那就不必要选择原来畸形的树根而去选择树的重心来保证深度.
* 树的重心:
* 树中一个最大子树 大小(size) 最小的点
* 树的重心的最大子树的size(mxsize) < <script type="math/tex" id="MathJax-Element-4"><</script>SIZE/2
* 证明:
* 设重心为x,最大子树为t.
* 假设size>SIZE/2,
* 那么如果重心为t,
mxsize为 size-1,size-1 < <script type="math/tex" id="MathJax-Element-5"><</script>size.
* 所以x不是重心.
* 所以重心的mxsize小于SIZE/2.

所以,这样复杂度就从 O(n) ,降为 O(logn) .



树链刨分:

(由于我只会轻重路径刨分,所以只讨论轻重路径刨分)

  • 可以把树分为若干重路径和轻边.
    • 从点v向size最大的点u连的一条边为重边,其余为轻边.
    • 全为重边构成的路径为重路径.
  • 它有三个性质:
    1. e(v,to) 为轻边,size(to) <= <script type="math/tex" id="MathJax-Element-9"><=</script>size(v)/2.
    2. 从根到任意点只有 logn 条轻边:
      • 由于size(to) <= <script type="math/tex" id="MathJax-Element-11"><=</script>size(v)/2.
      • 所以经过最多 logn 条size就会变为1.
      • 所以最多 logn 条轻边.
    3. 从根到任意点只有 logn 条重边:
      • n条重边由n-1条轻边连接.
      • 从根到任意点只有 logn 条轻边.
      • 所以从根到任意点只有 logn 条重边.
        这里写图片描述
        所以树链刨分的各种操作(不使用其他数据结构)的复杂度都为 logn .

树链刨分的用途:
1.LCA:

  • 每个点记录 fa[x] 和 top[x] (链的端点).
    • 若top[x]=top[y],说明x,y在同一条链上.LCA为深度较小的一个点.
    • 否则,使 fa[top[x]]深度大的点跳到fa[top[x]]
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <iostream>
#include <algorithm>
#define debug(x) cout<<#x<<" : "<<x<<endl
using namespace std;

const int M=100005;

template <class T> void Rd(T &res) {
    res=0;
    char c;
    while (c=getchar(),!isdigit(c));
    do res=(res<<1)+(res<<3)+(c^48);
    while (c=getchar(),isdigit(c));
}

struct node {
    int to;
    node *nxt;
};
struct graph {
    node *st[M];
    void add(int x,int y) {
        node *now=new node;
        now->to=y;
        now->nxt=st[x];
        st[x]=now;
    }
    int sz[M],fa[M],top[M],dep[M];
    void dfs(int x,int f,int d) {
        sz[x]=1,fa[x]=f,dep[x]=d;
        for (node *i=st[x];i;i=i->nxt) {
            int to=i->to;
            if (to==f) continue;
            dfs(to,x,d+1);
            sz[x]+=sz[to];
        }
    }

    void rdfs(int x,int f,int tp) {
        top[x]=tp;
        int id=0;
        for (node *i=st[x];i;i=i->nxt) {
            int to=i->to;
            if (to==f) continue;
            if (sz[to]>sz[id]) id=to;
        }
        for (node *i=st[x];i;i=i->nxt) {
            int to=i->to;
            if (to==f) continue;
            if (to==id) rdfs(to,x,tp);
            else rdfs(to,x,to);
        }
    }

    int LCA(int x,int y) {
        if (dep[x]>dep[y]) swap(x,y);
        if (top[x]==top[y]) return x;
        int nxtx=fa[top[x]];
        int nxty=fa[top[y]];
        if (dep[nxtx]>dep[nxty]) return LCA(nxtx,y);
        else return LCA(x,nxty);
    }
} g;


int main() {
    int n,m;
    Rd(n),Rd(m);
    for (int i=1,x,y;i<n;++i) {
        Rd(x),Rd(y);
        g.add(x,y);
        g.add(y,x);
    }
    g.dfs(1,1,1);
    g.rdfs(1,1,1);

    for (int i=1,x,y;i<=m;++i) {
        Rd(x),Rd(y);
        printf("%d\n",g.LCA(x,y));
    }
    return 0;
}

2.关于树形态不变,而边权点权改变的问题.

  • 在dfs刨分时,记录id,让重链的编号连续.
  • 用线段树维护编号对应权值.
struct seg_tree {
    int add[M<<2];
    void clear() {
        memset(add,0,sizeof(add));
    }
    void down(int p) {
        add[p<<1]+=add[p];
        add[p<<1|1]+=add[p];
        add[p]=0;
    }
    void update(int x,int st,int ed,int L=1,int R=M-1,int p=1) {
        if (st==L&&ed==R) {
            add[p]+=x;
            return;
        }
        int mid=(L+R)>>1;
        if (ed<=mid) update(x,st,ed,L,mid,p<<1);
        else if (st>mid) update(x,st,ed,mid+1,R,p<<1|1);
        else update(x,st,mid,L,mid,p<<1),update(x,mid+1,ed,mid+1,R,p<<1|1);
    }
    int query(int x,int L=1,int R=M-1,int p=1) {
        if (L==R) return add[p];
        down(p);
        int mid=(L+R)>>1;
        if (x<=mid) return query(x,L,mid,p<<1);
        else return query(x,mid+1,R,p<<1|1);
    }
};

struct node {
    int to;
    node *nxt;
};
struct graph {
    node *st[M];
    seg_tree tree;
    void add(int x,int y) {
        node *now=new node;
        now->to=y,now->nxt=st[x],st[x]=now;
    }
    int sz[M],fa[M],top[M],dep[M],ID[M];
    void dfs(int x,int f,int d) {
        sz[x]=1,fa[x]=f,dep[x]=d;
        for (node *i=st[x];i;i=i->nxt) {
            if (i->to==f) continue;
            dfs(i->to,x,d+1);
            sz[x]+=sz[i->to];
        }
    }
    int tot;
    void rdfs(int x,int f,int tp) {
        ID[x]=++tot;
        top[x]=tp;
        int id=0;
        for (node *i=st[x];i;i=i->nxt) {
            if (i->to==f) continue;
            if (sz[i->to]>sz[id]) id=i->to;
        }
        if (id) rdfs(id,x,tp);
        for (node *i=st[x];i;i=i->nxt) {
            if (i->to==f||i->to==id) continue;
            rdfs(i->to,x,i->to);
        }
    }
    void Init() {
        tot=0;
        dfs(1,1,1),rdfs(1,1,1);
    }
    void ADD(int k,int x,int y) {
        if (dep[x]>dep[y]) swap(x,y);
        if (top[x]==top[y]) {
            tree.update(k,ID[x],ID[y]);
            return;
        }
        int nxtx=top[x],nxty=top[y];
        if (dep[nxtx]>dep[nxty]) {
            tree.update(k,ID[nxtx],ID[x]);
            return ADD(k,fa[nxtx],y);   
        }
        else {
            tree.update(k,ID[nxty],ID[y]);
            return ADD(k,x,fa[nxty]);
        }
    }
    int QUE(int x) {
        return tree.query(ID[x]);   
    }
    void clear() {
        tree.clear();
        for (int i=0;i<M;++i) {
            for (node *j=st[i];j;) {
                node *nxt=j->nxt;
                delete j;
                j=nxt;
            }
            st[i]=NULL;
        }
    }
} g;

int val[M];

void solve(int n,int m,int p) {
    g.clear();
    for (int i=1;i<=n;++i) {
        Rd(val[i]);
    }
    for (int i=1,x,y;i<=m;++i) {
        Rd(x),Rd(y);
        g.add(x,y),g.add(y,x);
    }
    g.Init();
    for (int i=1,a,b,c;i<=p;++i) {
        char s[10];
        scanf("%s",s);
        if (s[0]=='I'||s[0]=='D') {
            Rd(a),Rd(b),Rd(c);
            if (s[0]=='I') g.ADD(c,a,b);
            else g.ADD(-c,a,b);
        } else {
            Rd(a);
            printf("%d\n",g.QUE(a)+val[a]);
        }
    }
}

(上面是HDU3966的代码)
3. …………..

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值