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

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

问题引入:

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

给定一颗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]中间的点。我们看下面这个例子。

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

有了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\n",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\n");

   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\n", 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\n", 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;

}


 

 

 



 

你可能感兴趣的:(spring)