树状数组 + 离散化 + dfs HDU 5877

Weak Pair

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 3893    Accepted Submission(s): 1165


Problem Description
You are given a  rooted  tree of  N  nodes, labeled from 1 to  N . To the  i th node a non-negative value  ai  is assigned.An  ordered  pair of nodes  (u,v)  is said to be  weak  if
  (1)  u  is an ancestor of  v  (Note: In this problem a node  u  is not considered an ancestor of itself);
  (2)  au×avk .

Can you find the number of weak pairs in the tree?
 

Input
There are multiple cases in the data set.
  The first line of input contains an integer  T  denoting number of test cases.
  For each case, the first line contains two space-separated integers,  N  and  k , respectively.
  The second line contains  N  space-separated integers, denoting  a1  to  aN .
  Each of the subsequent lines contains two space-separated integers defining an edge connecting nodes  u  and  v  , where node  u  is the parent of node  v .

  Constrains: 
  
   1N105  
  
   0ai109  
  
   0k1018
 

Output
For each test case, print a single integer on a single line denoting the number of weak pairs in the tree.
 

Sample Input
  
  
1 2 3 1 2 1 2
 

题意:有一棵树,树上每个点有一个权值,然后满足以下两个条件的两个点之间形成一个对子 (a[u],a[v])

1. u 节点是 v 节点的祖先节点

2. a[u] * a[v] <= k

求这棵树满足条件的对子数

思路:首先观察题目的两个条件,第一个 u 是 v 的祖先节点,那么也就是说 要从上往下考虑,但是你会发现,不行啊,从上往下,我的兄弟节点不是我的祖先节点啊,可是他会在我前面出现,也会在我后面,那怎么办呢,想一下,你从此节点,向上找祖先节点的时候,发现,它是一条直线往上的,并且只有一条路径,因为每个节点的父亲只有一个,那就好办了,这不就是 dfs序吗,但 dfs 序是先从根节点开始,从上往下的,没关系,先让它从上往下搜,搜到叶子节点的时候,这条dfs路径上经过的点,不正好全都是这个叶子节点的祖先节点吗?,那就好办了,就dfs的时候一边计数,到达叶子节点之后就计算一下,然后返回的时候删掉叶子节点的贡献,返回去继续计数,妥妥的。

用一个图直观地看看吧


详细作法:首先考虑到题目给的节点的值会非常的大,要把这些值update到树状数组里的前提条件是开的了那么大的数组,显然不行,又注意到点的个数不超过 10^5 个,所以实际上并没有那么多的数值,所以可以离散化来处理

这里介绍一个比较方便的离散化方法,用到 unique()函数 ,它是去重算法,可以把相邻的相同的数刨去,然后返回剩余数值个数,所以我们先 sort 排序一下输入的值,然后用这个东西就能把数值转化为 1 到 cnt 的数了

void discre(){	 // 离散化 
	sort(tm + 1,tm + 1 + n);
	cnt = unique(tm + 1,tm + 1 + n) - tm - 1;
}

然后我们找到根节点,从根节点开始进行 dfs,然后使用upper_bound() 函数,它是用来找到某个数组里某个值第一次出现的下标的,拿它到 unique 处理过的 tm[] 数组里找 当前节点的值 a[x] 的下标,就可以拿这个下标的值来代替原本的值,这里就实现了离散化。找到这个值后,把它 update 进树状数组,然后对儿子节点继续 dfs ,直到叶子节点的时候 ,把自身的值从树状数组里刨去,然后 要是 a[x] * a[v] <= k ,也就是要找到 小于等于 k / a[x] 的值 tmp

if(a[x] == 0)
	tmp = tm[cnt] + 1;	// a[u] * a[v] <= k 当 a[u] = 0的时候,全部都满足,所以给它一个最大值 + 1 
else
	tmp = k / a[x];		// 否则就 k / a[x] 找到最大的满足的值,然后在这个值之前都是满足的 
if(tmp > tm[cnt])
	tmp = tm[cnt] + 1;

这里分情况 : 当 a[x] = 0 的时候,所有的情况都符合,所以再大的值也可以满足条件,所以要给 tmp 一个足够大的值,前面 tm数组里 tm[cnt]是最大的值,所以 让 tmp = tm[cnt] + 1就可以了, 然后 upper_bound() 找tmp值找不到就会返回 end()尾指针,也就是最大的了,然后到树状数组里查询就可以了,树状数组记录的是前缀和,所以直接查询 tmp 就可以得到比它小的值的个数,这个个数就是计数要找的

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#include<cstring>
using namespace std;
#define ll long long
#define mem(a,x) memset(a,x,sizeof(a))
#define lowbit(x) (x & (-x))
#define maxn 110005

ll n,tm[maxn],tree[maxn],a[maxn],k,ans,cnt,dep[maxn];
vector<int>vec[maxn];

void update(int x,int v){
	while(x <= n){
		tree[x] += v;
		x += lowbit(x);
	}
}
ll getSum(int x){
	ll tmp = 0;
	while(x){
		tmp += tree[x];
		x -= lowbit(x);
	}
	return tmp;
}
int find(ll x){
	return upper_bound(tm + 1,tm + 1 + cnt,x) - tm - 1; // 找到第一个该值的点的下标
}
void dfs(int x){
	update(find(a[x]),1);	// 把值离散化为小值之后放进树状数组 
	int len = vec[x].size();
	for(int i = 0;i < len;i++){
		dfs(vec[x][i]);
	}
	update(find(a[x]),-1);   //本节点是处理到的最尾节点了,把值去掉 
	ll tmp;
	if(a[x] == 0)
		tmp = tm[cnt] + 1;	// a[u] * a[v] <= k 当 a[u] = 0的时候,全部都满足,所以给它一个最大值 + 1 
	else
		tmp = k / a[x];		// 否则就 k / a[x] 找到最大的满足的值,然后在这个值之前都是满足的 
	if(tmp > tm[cnt])
		tmp = tm[cnt] + 1;
	ans += getSum(find(tmp));
} 
void discre(){	 // 离散化 
	sort(tm + 1,tm + 1 + n);
	cnt = unique(tm + 1,tm + 1 + n) - tm - 1;
}
void init(){
	mem(dep,0);
	mem(tree,0);
	mem(vec,0);	
}
int main(){
	int u,v,t;
	scanf("%d",&t);
	while(t--){
		ans = 0;
		scanf("%lld %lld",&n,&k);
		for(int i = 1;i <= n;i++){
			scanf("%lld",&a[i]);
			tm[i] = a[i];
		}
		discre(); //离散化 
		init();	
		for(int i = 1;i < n;i++){
			scanf("%d %d",&u,&v);
			vec[u].push_back(v);
			dep[v]++;	//深度 
		}
		for(int i = 1;i <= n;i++){
			if(dep[i] == 0){
				dfs(i); //找到根节点 dfs 
				break;
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值