HDU-3387 Counting Offspring (树状数组+dfs序)

题目大意

  给出一颗树,求树每个结点的子树中编号小于结点的个数;

思路分析

 根据题意可以看出该题与用树状数组求逆序和的思路相同,因而我们通过dfs序把树区间化,根据dfs性质可知同一棵子树一一定对应dfs序中连续的一段;那么就可以树状数组算出子树中比根(子树)结点小的结点个数---- 求逆序和思想;

样例

//input:
15 7
7 10
7 1
7 9
7 3
7 4
10 14
14 2
14 13
9 11
9 6
6 5
6 8
3 15
3 12
0 0
output:
0 0 0 0 0 1 6 0 3 1 0 0 0 2 0

根据样例画出子树:

通过dfs序:

\\dfs序模板
\\ in[] 进,out[],tot = 0;
void dfs(int cur,int fa)
{
    
    
	in[cur] = ++ tot;
	for\\ 前向星遍历
	{
    
    
		int v  = e[i].to;
		if(v != fa)
			dfs(v,cur);
	}
	out[cur] = tot;
}

得到该树形成的区间

7 4 3 12 15 9 6 8 5 11 1 10 14 13 2

后通过类似于树状数组求逆序和的方法求解该问题

ACcode

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>

using namespace std;
const int N = 1e6+100;
int n,m;
int h[N],cnt;
struct node{
    
    
	int to,ne;
}e[N]; 
int in[N],out[N],t[N];
int tnt ;
void add(int u,int v){
    
    
	e[cnt].to = v;
	e[cnt].ne = h[u];
	h[u] = cnt++;
}
int ans[N],Node[N];
void dfs(int cur,int fath)
{
    
    
	in[cur] = ++tnt; 
	for(int i = h[cur];i!=-1;i=e[i].ne){
    
    
		int v = e[i].to;
		if(v != fath)
			dfs(v,cur);
	}
	out[cur] = tnt;
}

int lowbit(int x)
{
    
    
	return x&(-x);
}

void add(int x)
{
    
    
	while(x <= n){
    
    
		t[x] += 1;
		x += lowbit(x);
	}	
}

int getsum(int x){
    
    
	int res = 0;
	while(x>0){
    
    
		res += t[x];
		x -= lowbit(x);
	}
	return res;
}
void init()
{
    
    
	memset(ans,0,sizeof(ans));
	memset(h,-1,sizeof(h));
	memset(t,0,sizeof(t));
	cnt = tnt = 0;	
}
int main()
{
    
    
	while(scanf("%d%d",&n,&m) != EOF)
	{
    
    
		if(n == 0 && m == 0) break;
		init(); 
		int l,r;
		for(int i = 1;  i<= n-1;i++){
    
    
			 scanf("%d%d",&l,&r);
			 add(l,r);
			 add(r,l);
		}
		dfs(m,0);
		\\注意点: 先查询后插入
		for(int i = 1; i <= n; i++)
		{
    
    
			ans[i] = getsum(out[i])	- getsum(in[i]-1);
			add(in[i]);
		}
		
		for(int i = 1; i <= n;i++)
		{
    
    
			if(i == n){
    
    
				printf("%d\n",ans[i]);
				break;
			}
			printf("%d ",ans[i]);	
		}	
	}
	return 0;
}

注意点:
1.若先插入ans[]的最小值必为1是由于通过dfs序 g e t s u m ( o u t [ i ] ) − g e t s u m ( i n [ i ] − 1 ) getsum(out[i]) - getsum(in[i]-1) getsum(out[i])getsum(in[i]1)得到的结果就是 t [ o u t [ i ] ] = 1 t[out[i]] = 1 t[out[i]]=1的值,即是说当前i的插入对本身求解无贡献;
2.我们通俗的说一下树状数组两个操作add和getsum的作用:
我认为两者必须配对使用,即两者见的t数组并无实际作用,只是起到传递
作用,本质上树状数组是用来维护前缀和的,因而getsum的返回值才用实际用处;
add的作用就是在原数组上进行单点修改,getsum就是前缀和;
那么我们从编号1到n开始查询答案,可以想到1的答案定是0,后通过add标记1所在dfs区间的位置(标记操作为+1),同时形象化add(x)为插入到区间x位置上,那么在1所在位置为11,该位置+1;
同样的,会从 1~n按 顺序插入到对应位置上,getsum(x)是1~x 的和,即是说是比x小的数的总和
当标记为1是getsum就是比x小的个数;同时注意编号和dfs区间的对应关系,这里的x指的是编号对应的dfs关系;

理解有误希望指正,求求了 ORZ;

猜你喜欢

转载自blog.csdn.net/qq_51687628/article/details/117337658