zoukankan      html  css  js  c++  java
  • 树链剖分&dfs序

    树上问题

      很多处理区间的问题(像是RMQ,区间修改)。可以用线段树,树状数组,ST表这些数据结构来维护。但是如果将这些问题挪到了树上,就不能直接用这些数据结构来处理了。这时就用到了dfs序和树链剖分。
      

    DFS序

      dfs序就是按照dfs的顺序对一棵树上的结点进行编号。这样完成编号的优点是:每棵子树上的结点的编号都是连续的,这要只要记录下一棵子树开始的结点编号,和结束的节点编号,然后就可以用线段树等来维护这个有序数列了。

      dfs序主要是用于处理对于整棵子树的修改,像是子树每个节点权值加减v。查询树上某个点的值。。。。。。

    一道例题:

    poj3321

    思路

    进行一遍dfs,找出dfs序。因为是单点修改区间查询,所以可以直接用树状数组来维护。注意里面查询的时候的写法是,用子树结尾点的编号的前缀和减去开始编号的前一个编号的前缀和。

    代码:

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define lob(x) x&(-x)
    using namespace std;
    const int N=100000*2;
    int head[N],ejs;
    struct node
    {
        int v,nxt;
    }edg[N];
    void add(int u,int v)
    {
        edg[ejs].v=v;edg[ejs].nxt=head[u];head[u]=ejs;ejs++;
    }
    int tot,tree[N],n,in[N],out[N],app[N],m;
    void dfs(int x)
    {
        in[x]=++tot;
        for(int i=head[x];i!=-1;i=edg[i].nxt)
            dfs(edg[i].v);
        out[x]=tot;
    }
    void change(int pos,int x)
    {
        for(int i=pos;i<=tot;i+=lob(i))
            tree[i]+=x;
    }
    int find(int pos)
    {
        int ans=0;
        for(int i=pos;i>=1;i-=lob(i))
            ans+=tree[i];
        return ans;
    
    }
    int main()
    {
        memset(head,-1,sizeof(head));
        scanf("%d",&n);
        for(int i=1,x,y;i<n;++i)
        {
            scanf("%d%d",&x,&y);
            add(x,y);
        }
        dfs(1);
    
        for(int i=1;i<=n;++i)
        {
            app[i]=1;
            change(in[i],1);
        }
        scanf("%d",&m);
        char bz;
        int x;
        for(int i=1;i<=m;++i)
        {
            cin>>bz>>x;
            if(bz=='C')
            {
                if(app[x]!=0)
                {
                    change(in[x],-1);
                    app[x]=0;
                }
                else
                    change(in[x],1),app[x]=1;
            }
            else
            {
                printf("%d
    ",find(out[x])-find(in[x]-1));
            }
        
        }
       
        return 0;
    }
    

    树链剖分

      树链剖分其实就是为了更加便利的解决树上的问题,将树拆成链,然后对于拆成的链就可以用线段树,树状数组……来维护,以降低复杂度。
      做法就是先进行两遍dfs处理出七个数组来维护一些需要的东西。

    第一遍dfs处理的内容:

    • dep: 维护每个点的深度,这个在进行最后操作的时候要用
    • son: 顾名思义,就是来存当前点的子节点,但是这个子节点是指重儿子(以该儿子为根的子树是所有儿子子树中最大的)。
    • faz: 有儿子就有爹,这个也是以后进行操作的时候用
    • siz: 用来存以当前节点为根的子树的大小,最后利用这个来找重儿子

    第二遍dfs处理的内容:

    • rank: 将节点原来的编号与按照先搜重儿子的顺序的搜索编号进行对应
    • wt: 用来对应原节点的权值
    • top: 用来记录每个节点所在树链的顶端节点是谁

    具体的方法见代码:

    void dfs1(int u,int fa,int depth) {
    	f[u]=fa;
    	d[u]=depth;
    	size[u]=1;
    	for(int i=head[u]; i; i=e[i].next) {
    		int v=e[i].to;
    		if(v==fa)
    			continue;
    		dfs1(v,u,depth+1);
    		size[u]+=size[v];
    		if(size[v]>size[son[u]])
    			son[u]=v;
    	}
    }
    void dfs2(int u,int t) {
    	top[u]=t;
    	id[u]=++cnt;
    	rk[cnt]=u;
    	if(!son[u])
    		return;
    	dfs2(son[u],t);
    	for(int i=head[u]; i; i=e[i].next) {
    		int v=e[i].to;
    		if(v!=son[u]&&v!=f[u])
    			dfs2(v,v);
    	}
    }
    

    查询与修改:

      现在树上节点的编号就被我们对应成了连续的即rank数组。然后就可以用一科线段树或者其他数据结构来维护了。那么怎么保证我们能正确的进行查询与修改呢。
      举个栗子,如果让我们修改从节点a到节点b这条路径上节点的长度。如果a和b在同一条链上,就很好办了,因为他们对应的rank数组是连续的。但是更短情况下a与b是不连续的。我们可以将不连续转化为连续,只要a与b不在同一条链上(也就是他们的top不同),就将其中深度(dep)更深的那个向上跳,也就是先修改这个点的top节点到其这一段,然后将这个节点变为他top节点的faz。不断进行上述操作,直到a与b处在了同一条链上,这时修改a到b然后就完成了整个修改。
      查询操作同理,不断向上跳,同时查询途经节点的值。

    具体做法见代码:

    #include<iostream>
    #include<cstdio>
    #define int long long
    using namespace std;
    const int maxn=1e5+10;
    struct edge{
        int next,to;
    }e[2*maxn];
    struct Node{
        int sum,lazy,l,r,ls,rs;
    }node[2*maxn];
    int rt,n,m,r,p,a[maxn],cnt,head[maxn],f[maxn],d[maxn],size[maxn],son[maxn],rk[maxn],top[maxn],id[maxn];
    int mod(int a,int b)
    {
        return (a+b)%p;
    }
    void add_edge(int x,int y)
    {
        e[++cnt].next=head[x];
        e[cnt].to=y;
        head[x]=cnt;
    }
    void dfs1(int u,int fa,int depth)
    {
        f[u]=fa;
        d[u]=depth;
        size[u]=1;
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(v==fa)
                continue;
            dfs1(v,u,depth+1);
            size[u]+=size[v];
            if(size[v]>size[son[u]])
                son[u]=v;
        }
    }
    void dfs2(int u,int t)
    {
        top[u]=t;
        id[u]=++cnt;
        rk[cnt]=u;
        if(!son[u])
            return;
        dfs2(son[u],t);
        for(int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if(v!=son[u]&&v!=f[u])
                dfs2(v,v);
        }
    }
    void pushup(int x)
    {
        node[x].sum=(node[node[x].ls].sum+node[node[x].rs].sum+node[x].lazy*(node[x].r-node[x].l+1))%p;
    }
    void build(int li,int ri,int cur)
    {
        if(li==ri)
        {
            node[cur].l=node[cur].r=li;
            node[cur].sum=a[rk[li]];
            return;
        }
        int mid=(li+ri)>>1;
        node[cur].ls=cnt++;
        node[cur].rs=cnt++;
        build(li,mid,node[cur].ls);
        build(mid+1,ri,node[cur].rs);
        node[cur].l=node[node[cur].ls].l;
        node[cur].r=node[node[cur].rs].r;
        pushup(cur);
    }
    void update(int li,int ri,int c,int cur)
    {
        if(li<=node[cur].l&&node[cur].r<=ri)
        {
            node[cur].sum=mod(node[cur].sum,c*(node[cur].r-node[cur].l+1));
            node[cur].lazy=mod(node[cur].lazy,c);
            return;
        }
        int mid=(node[cur].l+node[cur].r)>>1;
        if(li<=mid)
            update(li,ri,c,node[cur].ls);
        if(mid<ri)
            update(li,ri,c,node[cur].rs);
        pushup(cur);
    }
    int query(int li,int ri,int cur)
    {
        if(li<=node[cur].l&&node[cur].r<=ri)
            return node[cur].sum;
        int tot=node[cur].lazy*(min(node[cur].r,ri)-max(node[cur].l,li)+1)%p;
        int mid=(node[cur].l+node[cur].r)>>1;
        if(li<=mid)
            tot=mod(tot,query(li,ri,node[cur].ls));
        if(mid<ri)
            tot=mod(tot,query(li,ri,node[cur].rs));
        return tot%p;
    }
    int sum(int x,int y)
    {
        int ans=0;
        int fx=top[x],fy=top[y];
        while(fx!=fy)
        {
            if(d[fx]>=d[fy])
            {
                ans=mod(ans,query(id[fx],id[x],rt));
                x=f[fx],fx=top[x];
            }
            else
            {
                ans=mod(ans,query(id[fy],id[y],rt));
                y=f[fy],fy=top[y];
            }
        }
        if(id[x]<=id[y])
            ans=mod(ans,query(id[x],id[y],rt));
        else
            ans=mod(ans,query(id[y],id[x],rt));
        return ans%p;
    }
    void updates(int x,int y,int c)
    {
        int fx=top[x],fy=top[y];
        while(fx!=fy)
        {
            if(d[fx]>=d[fy])
            {
                update(id[fx],id[x],c,rt);
                x=f[fx],fx=top[x];
            }
            else
            {
                update(id[fy],id[y],c,rt);
                y=f[fy],fy=top[y];
            }
        }
        if(id[x]<=id[y])
            update(id[x],id[y],c,rt);
        else
            update(id[y],id[x],c,rt);
    }
    signed main()
    {
        scanf("%d%d%d%d",&n,&m,&r,&p);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            add_edge(x,y);
            add_edge(y,x);
        }
        cnt=0;
        dfs1(r,0,1);
        dfs2(r,r);
        cnt=0;
        rt=cnt++;
        build(1,n,rt);
        for(int i=1;i<=m;i++)
        {
            int op,x,y,z;
            scanf("%lld",&op);
            if(op==1)
            {
                scanf("%lld%lld%lld",&x,&y,&z);
                updates(x,y,z);
            }
            else if(op==2)
            {
                scanf("%lld%lld",&x,&y);
                printf("%lld
    ",sum(x,y));
            }
            else if(op==3)
            {
                scanf("%lld%lld",&x,&z);
                //子树也有连续区间的性质
                update(id[x],id[x]+size[x]-1,z,rt);
            }
            else if(op==4)
            {
                scanf("%lld",&x);
                printf("%lld
    ",query(id[x],id[x]+size[x]-1,rt));
            }
        }
        return 0;
    }
    
    
    
  • 相关阅读:
    Oracle decode函数
    Flink笔记
    httpclient之put 方法(参数为json类型)
    XMLHTTPRequest的理解 及 SpringMvc请求和响应xml数据
    SQL获取本周,上周,本月,上月第一天和最后一天 注:本周从周一到周天
    Other
    Sql根据起止日期生成时间列表
    sql 在not in 子查询有null值情况下经常出现的陷阱
    sql 判断一个表的数据不在另一个表中
    查看系统触发器
  • 原文地址:https://www.cnblogs.com/wxyww/p/9432962.html
Copyright © 2011-2022 走看看