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

来源:互联网 发布:2016域名价格排行榜 编辑:程序博客网 时间:2024/05/21 18:00

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 ith 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
12 31 21 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 elsetmp = 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 110005ll 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 elsetmp = 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;}

阅读全文
1 0
原创粉丝点击