题意:
给出一棵n
个结点的树和一个数k
, 每个节点上有权值
, 问有多少个有序对(u,v) (u,v)
满足u
是v
的祖先, a[u] * a[v] <=K;
思路:
首先这是在树上进行操作,然后就是找满足祖先关系,并且a[u] * a[v] <=K; 的点的个数。
找点的a[u] * a[v] <=K,可以枚举每一个点然后查找他的孩子中的值小于等于k/a[u] 的点的个数,最后全部统计起来就行了。
然后这就转化成找树上点有多少个小于某个值得问题了,对于这种问题要转换成树状数组进行维护,时间复杂度就变成了n*log(n)了。
#include<bits/stdc++.h>
#define LL long long
#define bug puts("**********")
using namespace std;
const int N=110000;
LL k,a[N],sum[N],tree[N];
int num=0,n; ///离散化下标
LL ans=0;
int in[N];
vector<int>vec[N];
int lowbit(int x){
return x&(-x);
}
void add(int x,int d){
while(x<=n){
sum[x]+=d;
x+=lowbit(x);
}
}
LL getsum(int x){
LL tmp=0;
while(x){
tmp+=sum[x];
x-=lowbit(x);
}
return tmp;
}
int Find(LL x){
return upper_bound(tree+1,tree+1+num,x)-tree-1; ///别忘了减一
}
void dfs(int u){
add(Find(a[u]),1); ///表示这个结点出现过1次了(同时树状数组记录了前面出现过的 比这个结点小的 点的个数)
int len=vec[u].size();
for(int i=0;i<len;i++) dfs(vec[u][i]);
add(Find(a[u]),-1); ///防止影响到 不是祖先关系的其他结点(类似回溯,开始遍历u的兄弟结点)
LL tmp;
if(a[u]==0){
tmp=tree[num]+1;
}
else{
tmp=k/a[u];
}
if(tmp>tree[num])tmp=tree[num]+1;
ans+=getsum(Find(tmp));
}
int main(){
int t,u,v;
scanf("%d",&t);
while(t--){
ans=0;
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
tree[i]=a[i];
}
sort(tree+1,tree+n+1);
memset(sum,0,sizeof(sum));
memset(vec,0,sizeof(vec));
memset(in,0,sizeof(in));
num=1;
for(int i=2;i<=n;i++){ ///去重
if(tree[num]!=tree[i]){
tree[++num]=tree[i];
}
}
for(int i=0;i<n-1;i++){
scanf("%d%d",&u,&v);
vec[u].push_back(v);
in[v]++;
}
for(int i=1;i<=n;i++){
if(!in[i]){
dfs(i);break;
}
}
printf("%lld\n",ans);
}
}