树点分治与树链刨分

来源:互联网 发布:软件评测师视频 编辑:程序博客网 时间:2024/06/08 12:27

树分治:

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

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

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

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



树链刨分:

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

  • 可以把树分为若干重路径和轻边.
    • 从点v向size最大的点u连的一条边为重边,其余为轻边.
    • 全为重边构成的路径为重路径.
  • 它有三个性质:
    1. e(v,to)为轻边,size(to)<=size(v)/2.
    2. 从根到任意点只有logn条轻边:
      • 由于size(to)<=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<<endlusing 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