Find the answer(HDU多校第三场)

该博客介绍了如何使用权值线段树解决一道签到题。题目要求在给定的一组数中,删除前面的最小元素,使得剩余元素之和不超过特定值m。通过权值线段树,可以高效地找到接近目标差值的区间,实现求解策略。博客详细阐述了权值线段树的节点维护信息、两种查找情况以及叶子节点的处理方法,并提供了相应的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目
签到题。。。
大概意思是给你n个数给出m,对于每个书来说去掉前面最小的数使它和剩下的数的和小于等于m
权值线段树:
对于每个节点维护出这个区间内出现的点的次数和他们的和。
对于每个数来说我们不能删掉他自己所以剩下的数的最大的和就为m - a[i],那么我们在权值线段树中就找尽量和接近m - a[i],分为以下两种情况:
①当左子树的和小于当前值val,那么我们就在右子树中找val - sum[rt << 1],
②当左子树的和大于val,那就直接在左子树中继续找到接近val的区间,当我们找左子树时就说明右子树的结点都会被删掉,那么直接加上节点中维护的数的个数,
在叶子节点处我们用当前值val/a[i] 向下取整,就是我们在这个叶子节点要用的数的个数,用叶子节点的总个数减去它就是要删去的数的个数。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e5 + 56;
int sum[maxn << 2], num[maxn << 2];//区间和、每个数出现的次数之和
int a[ maxn];
vector<int > v;
void build(int l, int r ,int rt){
    sum[rt] = num[rt] = 0;
    if(l == r) return ;
    int mid = (l + r) >> 1;
    build(l, mid , rt <<1);
    build(mid + 1 , r , rt << 1|1);

}
void update(int l, int r ,int rt,int pos){
    if(l == r){
        sum[rt] += v[l - 1];
        num[rt] ++;
        return ;
    }
    int mid = (l + r) >> 1;
    if(mid >= pos) update(l , mid , rt << 1 , pos);
    else update(mid + 1 , r , rt << 1|1, pos);

    sum[rt] = sum[rt <<1] + sum[rt << 1 | 1];
    num[rt] = num[rt << 1] + num[rt << 1|1];
}
int query(int l, int r , int rt ,int val){
    if(l == r){//叶子节点
        return num[rt] - floor(val*1.0/v[l - 1]);
    }

    int mid = (l + r) >> 1;
    if(val <= sum[rt << 1]){//情况②
        return num[rt <<1 |1] + query(l , mid , rt << 1, val);
    }
    return query(mid + 1 , r , rt <<1| 1, val - sum[rt << 1]);//情况①
}
signed main()
{
    int t;
    cin >> t;
    while(t --){
        v.clear();
        int n,m;
        cin >> n >> m;
        for(int i = 0 ; i < n; i ++){
            cin >> a[i];
            v.push_back(a[i]);
        }
        sort(v.begin(), v.end());
        int p = unique(v.begin(), v.end()) - v.begin();
        int x = lower_bound(v.begin() , v.begin() + p , a[0]) - v.begin() + 1;
        build(1 , p  , 1);
        update(1, p  , 1 , x);
        cout << 0 <<" ";
        for(int i = 1; i < n ; i ++ ){

            if(sum[1] + a[i] <= m) cout << 0 << " ";
            else{
                cout << query(1, p  , 1 , m - a[i]) <<" ";
            }
            update(1, p , 1, lower_bound(v.begin() , v.begin() + p , a[i]) - v.begin() + 1);
        }
        cout <<endl;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值