HDU 5877 dfs+离散化+树状数组(树上维护)

题意:

 给出一棵n个结点的树和一个数k, 每个节点上有权值​​, 问有多少个有序对(u,v)  (u,v)满足uv的祖先, 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);
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值