题目
签到题。。。
大概意思是给你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;
}
}