Weak Pair
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)Total Submission(s): 3893 Accepted Submission(s): 1165
(1) u is an ancestor of v (Note: In this problem a node u is not considered an ancestor of itself);
(2) au×av≤k .
Can you find the number of weak pairs in the tree?
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:
1≤N≤105
0≤ai≤109
0≤k≤1018
1 2 3 1 2 1 2
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;
}