• Hdu 3887 Counting Offspring Poj 3321 Apple Tree BZOJ 1103 [POI2007]大都市meg


    这几个题练习DFS序的一些应用。

    问题引入:


    给定一颗n(n <= 10^5)个节点的有根树,每个节点标有权值,现有如下两种操作:
    1、C x y     以节点x的权值修改为y。
    2、Q x       求出以节点x为根的子树权值和。

    最直观的做法, 枚举一个子树内所有节点的权值加和。但这种做法的每一次讯问的时间复杂度是O(n)的,很明显无法满足题目的需要,我们需要更优的解法。

    我们考虑DFS序的另外一种形式,当访问到一个节点时记下当前的时间戳,我们设它为L[x],  结束访问一个节点时当前的时间戳设为R[x]。则以x为根的子树刚好是下标为L[x]和R[x]中间的点。我们看下面这个例子。


    有了DFS序这个性质我们就可以讲这个问题转化成一个区间求和问题。
    每一次询问的复杂度就是O(logn)级别的,完全可以接受。

    另外:很多人习惯在生成DFS序的时候,叶子节点的进出时间戳差1,我习惯于叶子节点的进出时间戳一样。这样根节点的时间戳为[1,n]

    比如说:上图中如果采用叶子节点进出时间戳差1的写法,DFS序为:4、3、1、7、7、1、2、6、6、2、3、5、8、8、5、4.数量为2*n

    如果按照我习惯的做法,DFS序为:4、3、1、7、2、6、5、8.

    两种做法都可以,我习惯于第二种。

    练习题:

    第一题:http://acm.hdu.edu.cn/showproblem.php?pid=3887

    题意:给定一棵树,求某一节点所在的子树中比它的值小的节点数量。

    思路:求给定节点的时间戳区间,则在此区间的DFS序就是该节点的子树中的所有节点。统计这个区间中的比给定节点值小的数目即可。

    这里我们可以变换一种思维,从后往前找。因为值最大的节点之间肯定都是比它小的。求出后删除掉,就不会影响小标号的节点统计了。

    区间维护用线段树或者树状数组均可。我用线段树。

    为了避免卡Vector,建树用前向星模拟一下。

    为了避免卡递归DFS爆栈,用手工模拟非递归DFS。

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <vector>
    #include <queue>
    #include <stack>
    using namespace std;
    
    #define Maxn 100005
    #define lx (x<<1)
    #define rx ((x<<1) | 1)
    #define MID ((l + r)>>1)
    
    int n,s;
    int total = 0;
    
    //前向星
    int first[Maxn];
    int next[Maxn<<1];
    struct Edge
    {
       int a,b;
    }edge[Maxn<<1];
    
    int S[Maxn<<2];
    int vis[Maxn];
    
    struct  Node
    {
       int l,r;
    }node[Maxn];
    
    int nodeNum = 0;
    int f[Maxn];
    stack <int> st;
    
    void insert(int a,int b)
    {
       total++;
       edge[total].a = a;
       edge[total].b = b;
       next[total] = first[a];
       first[a] = total;
    }
    //递归dfs会爆栈
    /*void dfs(int s)
    {
       nodeNum++;
       vis[s] = 1;
       node[s].l = nodeNum;
       for(int i=first[s];i!=-1;i=next[i])
       {
          if(!vis[edge[i].b]) dfs(edge[i].b);
       }
       node[s].r = nodeNum;
    }*/
    
    void dfs(int s)
    {
       st.push(s);
       while(!st.empty())
       {
          int flag = 0;
          int t = st.top();
          if(!vis[t])
          {
             nodeNum++;
             node[t].l = nodeNum;
             vis[t] = 1;   
          }
          for(int i=first[t];i!=-1;i=next[i])
          {
             if(!vis[edge[i].b])
             {
                st.push(edge[i].b);
                flag = 1;
             }
          }
          //叶子节点
          //把pop放在最后
          if(!flag)
          {
             node[t].r = nodeNum;
             st.pop();
          }
       }
    }
    //线段树相关
    void pushUp(int x)
    {
       S[x] = S[lx] + S[rx];
    }
    void build(int l,int r,int x)
    {
       if(l == r)
       {
          S[x] = 1;
          return;
       }
       build(l,MID,lx);
       build(MID+1,r,rx);
       pushUp(x);
    }
    void update(int p,int d,int l,int r,int x)
    {
       if(l == r)
       {
          S[x] += d;
          return;
       }
       if(p<=MID) update(p,d,l,MID,lx);
       else update(p,d,MID+1,r,rx);
       pushUp(x);
    }
    int query(int L,int R,int l,int r,int x)
    {
       if(L<=l && r<=R)
       {
          return S[x];
       }
       int ans = 0;
       if(L<=MID) ans += query(L,R,l,MID,lx);
       if(MID+1<=R) ans += query(L,R,MID+1,r,rx);
       return ans;
    }
    void init()
    {
       total = 0;
       nodeNum = 0;
       memset(first,-1,sizeof(first));
       memset(next,-1,sizeof(next));
       memset(vis,0,sizeof(vis));
       memset(f,0,sizeof(f));
       while(!st.empty())
       {
          st.pop();
       }
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("in2.txt","r",stdin);
    #endif
       int a,b;
       while(scanf(" %d %d",&n,&s)!=EOF)
       {
          init();
          if(n+s == 0) break;
          for(int i=0;i<n-1;i++)
          {
             scanf(" %d %d",&a,&b);
             insert(a,b);
             insert(b,a);
          }
          dfs(s);
          build(1,n,1);
          for(int i=n;i>=1;i--)
          {
             f[i] = query(node[i].l,node[i].r,1,n,1) - 1;
             update(node[i].l,-1,1,n,1);
          }
          for(int i=1;i<=n;i++)
          {
             i == n ? printf("%d
    ",f[i]) : printf("%d ",f[i]); 
          }
       }
       return 0;
    }
    

    第二题: http://poj.org/problem?id=3321

    题意是统计某一节点所在子树的节点个数。伴随着节点的删除增加操作。

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <vector>
    #include <queue>
    using namespace std;
    
    #define Maxn 100005
    #define lx (x<<1)
    #define rx ((x<<1) | 1)
    #define MID ((l + r)>>1)
    
    //卡Vector,改成前向星吧
    //点邻接的第一个边号
    int first[Maxn];
    int next[Maxn<<1];
    int edgeNum;
    struct Edge
    {
       int a,b;
    }edge[Maxn<<1];
    
    int vis[Maxn];
    int total = 0;
    int n,m;
    int S[Maxn<<2];
    
    struct Node
    {
       int l,r;
    }node[Maxn];
    
    void insert(int a,int b)
    {
       edgeNum++;
       edge[edgeNum].a = a;
       edge[edgeNum].b = b;
       next[edgeNum] = first[a];
       first[a] = edgeNum;
    }
    void dfs(int s)
    {
       //printf("hello world
    ");
       total++;
       node[s].l = total;
       vis[s] = 1;
       for(int i = first[s];i!=-1;i = next[i])
       {
          if(!vis[edge[i].b]) dfs(edge[i].b);
       }
       node[s].r = total;
    }
    void pushUp(int x)
    {
       S[x] = S[lx] + S[rx];
    }
    void build(int l,int r,int x)
    {
       if(l == r)
       {
          S[x] = 1;
          return;
       }
       build(l,MID,lx);
       build(MID+1,r,rx);
       pushUp(x);
    }
    int query(int L,int R,int l,int r,int x)
    {
       if(L<=l && r<=R) return S[x];
       int ans = 0;
       if(L<=MID) ans += query(L,R,l,MID,lx);
       if(MID+1<=R) ans += query(L,R,MID+1,r,rx);
       pushUp(x);
       return ans;
    }
    void update(int p,int d,int l,int r,int x)
    {
       if(l == r)
       {
          S[x] = d - S[x];
          return;
       }
       if(p<=MID) update(p,d,l,MID,lx);
       else update(p,d,MID+1,r,rx);
       pushUp(x);
    }
    
    void init()
    {
       total = 0;
       edgeNum = 0;
       memset(vis,0,sizeof(vis));
       memset(next,-1,sizeof(next));
       memset(first,-1,sizeof(first));
    }
    
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
    #endif
     
       int a,b,c;
       char op[5];
       init();
       scanf(" %d",&n);
       for(int i=0;i<n-1;i++)
       {
          scanf(" %d %d",&a,&b);
          insert(a,b);
          insert(b,a);
       }
       dfs(1);
       build(1,n,1);
       scanf(" %d",&m);
       for(int i=0;i<m;i++)
       {
          scanf(" %s %d",op,&c);
          if(op[0] == 'Q')
          {
             int ans = query(node[c].l,node[c].r,1,n,1);
             printf("%d
    ", ans);
          }
          else if(op[0] == 'C')
          {
             update(node[c].l,1,1,n,1);
          }
       }
       return 0;
    }

    第三题:http://www.lydsy.com/JudgeOnline/problem.php?id=1103

    某一节点到根节点距离的维护问题。

    思路:使用DFS O(n)预处理出所有节点到根的路径上的权值和,将一个点的权值增加b就相当于将这个以这个点为根的子树中所有节点到根的距离增加b,使用线段树的lazy标记。

    虽然此题是区间更新,单点查询。我们也可以用区间更新,区间查询的思路。

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <vector>
    #include <queue>
    #include <stack>
    #include <algorithm>
    using namespace std;
    
    #define Maxn 250005
    #define lx (x<<1)
    #define rx ((x<<1) | 1)
    #define MID ((l + r)>>1)
    
    int n,m;
    
    int first[Maxn];
    int next[Maxn];
    int father[Maxn];
    int vis[Maxn];
    int rank[Maxn];
    //本题处理成有向边,就不用开双倍内存了
    struct Edge
    {
       int a,b;
    }edge[Maxn];
    int edgeNum = 0;
    int totalNum = 0;
    
    
    struct Node
    {
       int l,r;
       int roadNum;
    }node[Maxn];
    
    //处理成有向边吧
    void insert(int a,int b)
    {
       edgeNum++;
       edge[edgeNum].a = a;
       edge[edgeNum].b = b;
       next[edgeNum] = first[a];
       first[a] = edgeNum;
    }
    void dfs(int s,int deep)
    {
       totalNum++;
       node[s].l = totalNum;
       rank[totalNum] = s;
       node[s].roadNum = deep;
       vis[s] = 1;
       for(int i=first[s];i!=-1;i=next[i])
       {
          if(!vis[edge[i].b]) dfs(edge[i].b,deep+1);
       }
       node[s].r = totalNum;
    }
    void init()
    {
       memset(first,-1,sizeof(first));
       memset(next,-1,sizeof(next));
       memset(father,0,sizeof(father));
       memset(vis,0,sizeof(vis));
       edgeNum = 0;
       totalNum = 0;
    }
    
    int S[Maxn<<2];
    int D[Maxn<<2];
    
    void pushUp(int x)
    {
       //S[x] = S[lx] + S[rx];
       //我们只需要叶节点的信息,写个伪的吧
       return;
    }
    void pushDown(int l,int r,int x)
    {
       if(D[x])
       {
          D[lx] += D[x];
          D[rx] += D[x];
          S[lx] += (MID-l+1)*D[x];
          S[rx] += (r-MID)*D[x];
          D[x] = 0;
       }
    }
    void build(int l,int r,int x)
    {
       if(l == r)
       {
          S[x] = node[rank[l]].roadNum;
          return;
       }
       build(l,MID,lx);
       build(MID+1,r,rx);
       pushUp(x);
    }
    void update(int L,int R,int d,int l,int r,int x)
    {
       if(L<=l && r<=R)
       {
          D[x] += d;
          S[x] += (r - l + 1)*d;
          return;
       }
       pushDown(l,r,x);
       if(L<=MID) update(L,R,d,l,MID,lx);
       if(MID+1<=R) update(L,R,d,MID+1,r,rx);
       pushUp(x);
    }
    int query(int L,int R,int l,int r,int x)
    {
       
       if(L<=l && r<=R)
       {
          return S[x];
       }
       int ans = 0;
       pushDown(l,r,x);
       if(L<=MID) ans += query(L,R,l,MID,lx);
       if(R>=MID+1) ans += query(L,R,MID+1,r,rx);
       return ans;
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
    #endif
       init();
       int a,b;
       int opa,opb;
       char op[5];
       scanf(" %d",&n);
       for(int i=0;i<n-1;i++)
       {
          scanf(" %d %d",&a,&b);
          if(a>b) swap(a,b);
          insert(a,b);
       }
       dfs(1,0);
       
       build(1,n,1);
    
       scanf(" %d",&m);
       m = n + m - 1;
       for(int i=0;i<m;i++)
       {
          scanf(" %s",op);
          if(op[0] == 'W')
          {
             scanf(" %d",&opa);
             int ans = query(node[opa].l,node[opa].l,1,n,1);
             printf("%d
    ", ans);
          }
          else if(op[0] == 'A')
          {
             scanf(" %d %d",&opa,&opb);
             if(opa>opb) swap(opa,opb);
             update(node[opb].l,node[opb].r,-1,1,n,1);
          }
       }
       return 0;
    }
    




  • 相关阅读:
    USACO 5.5 Twofive
    USACO 5.4 Telecowmunication
    USACO 5.4 Character Recognition
    BZOJ 1001: [BeiJing2006]狼抓兔子
    USACO 5.4 Canada Tour
    USACO 5.3 Big Barn
    USACO 5.3 Network of Schools
    USACO 5.3 Window Area
    2017辽宁冬令营-4.蚂蚁
    2017辽宁冬令营-3.斐波那契
  • 原文地址:https://www.cnblogs.com/riskyer/p/3235596.html
Copyright © 2020-2023  润新知