2019杭电多校第三场 Find the answer 6609(权值线段树)

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;
}
发布了51 篇原创文章 · 获赞 16 · 访问量 3366

猜你喜欢

转载自blog.csdn.net/weixin_43911945/article/details/99689644