hdu 5877

讨论

树状数组,离散化,深搜,比较朴素的思想就是深搜时对于每个节点都直接从树上找满足条件的祖先,铁定超时,想到如何提升每次查找满足条件的点数,线段树/树状数组,以之将查询复杂度降低到对数级,故树上存每个点的值即可,但又数据规模巨大,再离散化处理之,得解
实现层面,综合复杂度是线性对数级,绝大多数操作都是这个复杂度,没什么需要特别注意的地方或坑
题目的数据其实是比较水的,深搜层数也很有限,为此额roll了一组极端数据,100000个点,常数K也是最大值,而且所有点连成一条线,树没有任何分叉,本来想传上来,结果由于数据真的是太大而失败了,其实这个不用传,因为构造原理很简单,完全可以用java写个程序生成一组出来,都是差不多的

题解状态

1014MS,9624K,1573 B,C++

题解代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define MAXN 100005
#define memset0(a) memset(a,0,sizeof(a))

#define long long long
#define lb(a) (a&(-a))
int N, nums[MAXN];//节点总数 每个点的值
int to[MAXN], pre[MAXN], al[MAXN], index;//静态邻接表的四个数组 边指向的点 同起点上一条边下标 每个起点最后一条边的下标 分配下标的时间戳 这个al是adjacency_list
long K;//给定的常数
long hashx[MAXN * 2], hashn, tree[MAXN * 2];//离散化数组 以及离散化数组去重后的长度 不仅原值要存 K除以之的商也要存 以及树状数组 同样是2倍
bool not_root[MAXN];//非根节点都会标记为1
long query(int a)
{
    long sum = 0;
    while (a) {
        sum += tree[a];
        a -= lb(a);
    }
    return sum;
}
void modify(int a, int dv)
{
    while (a < MAXN * 2) {
        tree[a] += dv;
        a += lb(a);
    }
}
long dfs(int a)
{
    int border = lower_bound(hashx, hashx + hashn, K / nums[a]) - hashx, which;//二分找到最大可行值在离散数组的下标
    long cnt = query(border);//查一下多少个满足条件的祖先
    modify(which = lower_bound(hashx, hashx + hashn, nums[a]) - hashx, 1);//将这个点加入树状数组
    for (int p = al[a]; p; p = pre[p])
        cnt += dfs(to[p]);//对其子节点同样处理
    modify(which, -1);//这个点从树状数组删除
    return cnt;
}
long fun()
{
    for (int p = 1; p <= N; p++) {
        scanf("%d", &nums[p]);//input
        hashx[p] = nums[p];
        hashx[N + p] = K / hashx[p];
    }
    sort(hashx + 1, hashx + 2 * N + 1);
    hashn = unique(hashx + 1, hashx + 2 * N + 1) - hashx - 1;//这个-1不该有吧 忘了什么时候加的了
    index = 1;
    for (int p = 1; p < N; p++) {//由于只有一棵树 所以是N-1条边
        int a, b;
        scanf("%d%d", &a, &b);//input
        to[index] = b, pre[index] = al[a], al[a] = index++;
        not_root[b] = 1;
    }
    return dfs(find(not_root + 1, not_root + N + 1, 0) - not_root);//从根节点入手深搜
}
int main(void)
{
    //freopen("vs_cin.txt", "r", stdin);
    //freopen("vs_cout.txt","w",stdout);

    int times;
    scanf("%d", &times);//input
    while (times--) {
        scanf("%d%lld", &N, &K);//input
        printf("%lld\n", fun());//output
        memset0(not_root);
        memset0(al);
    }
}

EOF

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值