题意:给你n,m,然后n个数,对于每一个i来说 要求删掉区间[1,i) (注意这里是左闭右开区间) 最少的数使得区间[1,i]内数字的和小于等于m
当时就只想到了用优先队列模拟,可想而知 T T T ,后来知道用线段树,QAQ,死活想不到这种题需要用线段树写
思路:
要求删除最少的数,肯定是删除尽量大的数,换一个思维,也就是用尽量多的比较小的数来凑出一个小于等于m的数
由于删除的区间是左闭右开的 所以就相当于 用尽量多的比较小的数来凑出一个小于等于m-a[i]的数
由于变成的了区间求和的问题,所以用线段树维护一下每种数出现的次数与加和即可
每次更新一个点,单点更新,区间查询,查询的时候就直接查有多少个数 可以组成小于等于m-a[i]的,然后用当前的i-query-1
对于第一个数来说一定不用删掉,所以直接输出0就好
其他的数就是平常的查询了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int maxn = 2e5+7;
struct Tree {
int l,r,num;
ll sum;
}tree[maxn<<2];
ll a[maxn],b[maxn];
void pushup(int cur) {
tree[cur].sum = tree[cur<<1].sum+tree[cur<<1|1].sum;
tree[cur].num = tree[cur<<1].num+tree[cur<<1|1].num;
}
void build(int l,int r,int cur) {
tree[cur].l=l;
tree[cur].r=r;
tree[cur].num=0;
tree[cur].sum=0;
if(l==r)return ;
int mid = l + r >> 1;
build(l,mid,cur<<1);
build(mid+1,r,cur<<1|1);
pushup(cur);
}
void update(int l,int r,int cur,int pos) {
if(l==r) {
tree[cur].num++;
tree[cur].sum+=b[pos];
return ;
}
int mid = l + r >> 1;
if(pos<=mid) update(l,mid,cur<<1,pos);
else update(mid+1,r,cur<<1|1,pos);
pushup(cur);
}
int query(int l,int r,int cur,ll val) {
if(tree[cur].sum<=val) return tree[cur].num;
if(l==r) return val/b[l];
int mid = l + r >> 1;
if(val<=tree[cur<<1].sum) return query(l,mid,cur<<1,val);
else return tree[cur<<1].num + query(mid+1,r,cur<<1|1,val-tree[cur<<1].sum);
}
int main()
{
int T,n,m;
scanf("%d",&T);
while(T--) {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
scanf("%lld",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);
int len = unique(b+1,b+n+1)-(b+1);
build(1,len,1);
for(int i=1;i<=n;i++) {
if(i==1) printf("0 ");
else {
printf("%d ",i - 1 - query(1,len,1,m-a[i]));
}
int pos = lower_bound(b+1,b+n+1,a[i]) - b;
update(1,len,1,pos);
}
puts("");
}
return 0;
}