2019杭电多校第三场 Find the answer 6609(权值线段树)
题目
http://acm.hdu.edu.cn/showproblem.php?pid=6609
题意
给你n,m两个数表示接下来有n个数,要求求每个数的前缀和都要小于m。你还有一个能力,能使Wi前面的i-1个数变成0,要求每个数最少能把多少个数变成0,还满足小于等于m的条件。
题解
这道题可以用权值线段树来解决。因为数据范围大,所以还需要一下离散化。
离散化后的权值线段树:
每个节点维护出这个区间内出现的点的次数和他们的和。
然后每当Wi的前缀和大于m时我们就查找有 多少个数(ans) + Wi 小于等于 m。因为我们求出来的ans越大,要删除的数就越少,然后再用i减去ans就可以了.
代码
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn = 2e5 + 101;
const int maxm = 100 + 101;
const ll mod = 1e9 + 7;
int n, T;
ll m;
ll sum[maxn<<2], num[maxn<<2], a[maxn], b[maxn];
void update(int rt, int l, int r, int x)
{
if(l == r)
{
sum[rt] += b[x];
num[rt] ++;
return;
}
int mid = (l+r) >> 1;
if(x <= mid) update(rt << 1,l,mid,x);
else update(rt << 1 | 1,mid+1,r,x);
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
num[rt] = num[rt<<1] + num[rt<<1|1];
}
int query(int rt,int l, int r, ll ans)
{
if(l == r)
return ans/b[l];
int mid = (l+r)>>1;
if(ans <= sum[rt<<1])
return query(rt<<1,l,mid,ans);
else
return num[rt<<1] + query(rt<<1|1, mid+1, r, ans-sum[rt<<1]);
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d %lld",&n,&m);
for(int i = 1; i <= n;i++)
{
scanf("%lld",&a[i]);
b[i] = a[i];
}
sort(b+1,b+1+n);
int len = unique(b+1,b+1+n) -b-1;//去重
for(int i = 1;i<=(n<<2);i++)
sum[i] = num[i] = 0;
ll res = 0;
for(int i = 1; i <= n; i++)
{
res += a[i];
int ans = i;
int id = lower_bound(b+1, b+1+len, a[i]) - b;//查找a[i]的下标
if(res > m)
ans = query(1,1,len,m -a[i])+1;
update(1,1,len,id);
printf("%d ",i - ans);
}
puts("");
}
return 0;
}