计蒜客-青出于蓝胜于蓝 dfs+树状数组

 题目描述: 

武当派一共有 n人,门派内 n 人按照武功高低进行排名,武功最高的人排名第 1,次高的人排名第 2,... 武功最低的人排名第 n。现在我们用武功的排名来给每个人标号,除了祖师爷,每个人都有一个师父,每个人可能有多个徒弟。

我们知道,武当派人才辈出,连祖师爷的武功都只能排行到 p。也就是说徒弟的武功是可能超过师父的,所谓的青出于蓝胜于蓝。

请你帮忙计算每个人的所有子弟(包括徒弟的徒弟,徒弟的徒弟的徒弟....)中,有多少人的武功超过了他自己。

输入格式
输入第一行两个整数 n,p(1≤n≤100000,1≤p≤n)n, p(1 <= n <= 100000, 1 <= p <= n)n,p(1≤n≤100000,1≤p≤n)。

接下来 n−1n-1n−1 行,每行输入两个整数 u,v(1≤u,v≤n)u, v(1 <= u, v <= n)u,v(1≤u,v≤n),表示 u 和 v 之间存在师徒关系。

输出格式
输出一行 n 个整数,第 i 个整数表示武功排行为 i 的人的子弟有多少人超过了他。

行末不要输出多余的空格。
样例输入
10 5
5 3
5 8
3 4
3 1
2 1
6 7
8 7
9 8
8 10
样例输出
0 0 2 0 4 0 1 2 0 0

题解参考https://blog.csdn.net/wjhshuai/article/details/79546381

 题解:

如下图所示,对一颗树进行dfs,先搜索自己,然后是所有孩子节点。(以二叉树为例)

节点旁边的编号表示搜索的时序,从根节点开始搜,第一次搜的是1节点,然后搜2、4、8、5.....(图上只标注了一部分)

对于节点2来说,其子节点为搜索时序3,4,5的节点。

所以dfs的时候,每搜索到一个节点时,使用数组l[]标记其搜索时序,将子节点搜索完后,r[]数组标记回溯时的时序,这样就可以判断每个节点子节点的搜索时序范围。比如对2来说,dfs时序为2,搜完子节点回溯时时序为5,所以3-5时序的就是其子节点。

题目要求每一个节点子节点比自己小的个数,所以就可以将节点从1-n遍历,每次计算区间l[i]-r[i]的区间和sum(sum表示区间内比当前节点小的节点数),l[i]-r[i]区间就确保一定是i节点的子节点,因为要统计的节点一定比i小,所以每次遍历一个节点时,就将其时序l[i]的地方加1,这样后面求区间和时如果包含了之前加1的部分,那就可以统计进去。。(说得有点绕,具体看代码)

 

#include <bits/stdc++.h> 
#define MAXN 100005
using namespace std;
// 从1开始计数 
int C[MAXN]; // 树状数组

int head[MAXN];// i号节点的第一个子节点在结构体数组的下标为head[i]
int l[MAXN],r[MAXN];// l数组:记录dfs搜到i点时的次序,r数组:记录dfs回溯回i点时的时序 
int n,f;// 输入
int times;// 深度搜索的次序 
struct Node {
	int val,next;// 节点编号,下一个兄弟节点在node数组的下标	
}node[MAXN]; 
int num;// node数组元素数目 


// 取x最低位的1 
int lowbit(int x)  {
	return x & -x;
}

// pos下标处增加delta
void update(int pos,int delta) {	
	for(int i = pos;i <= n;i += lowbit(i)) {
		C[i] += delta;		
	}	
}

// 区间查询,查询前x项和 
int query(int x)  {
	int cnt = 0;
	for(int i = x;i >= 1;i -= lowbit(i)) {
		cnt += C[i];
	}	
	
	return cnt;
}

// 往图里加点
void add(int u,int v)  {
	node[num].next = head[u];
	node[num].val = v;	
	head[u] = num++;	
}

// 深度优先搜索,先搜完孩子节点,再搜本节点 
// cur:当前节点编号,father: 父节点编号(用来避免重复搜索) 
void dfs(int cur,int father) {
	l[cur] = ++times;
	// 遍历所有子节点 
	for(int i = head[cur];i != -1;i = node[i].next) {
		int son = node[i].val;// 子节点编号 
		// 深搜
		if(son != father) 
			dfs(son,cur);
	} 
	
	// 回溯时标记
	r[cur] = times;
}

int main() {
	scanf("%d%d",&n,&f);
	for (int i = 0;i <= n;i++) head[i] = -1;// 初始化	 			
	int u,v;
	for(int i = 1;i < n;i++) {
		scanf("%d%d",&u,&v);// u是v的师父 
		add(u,v);
		add(v,u);
	}
	dfs(f,-1);// 从根节点开始搜索 
	// 从第一名到最后一名遍历武林高手 
	for(int i = 1;i <= n;i++) {
		printf("%d\n",query(r[i])-query(l[i]));
		update(l[i],1);
	}
	
			
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值