2019 Multi-University Training Contest 3 Find the answer (离散化+二分+树状数组)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6609

题目大意:给定一个含有n个数的序列,还有一个m,对于每个i(1<=i<=n)求出最少需要将前i-1个数中的多少个数改成0,才能使得前i个数的和小于m

解题思路:很容易想到,我们应该将比较大的数变为0,答案才是最优的。所以我们直接给他们排个序离散化一下,对应它们在树上的编号,树上节点存两个东西,一个是节点的权值之和,还有一个就是包含数的个数,每次查询时,先将前i-1个数更新到树上,可以保证后面的数不影响答案,假设前i个数的和sum-m=x,则我们只要二分找到个一个最大的l,使得区间[l,n]大于等于x就可以了,这样答案就是区间[l,n]的数的个数了。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+7;
int n,m,ans[maxn];
struct node{
    int val,id,rk;
}a[maxn];
bool cmp1(node x,node y){
    return x.val<y.val;
}
bool cmp2(node x,node y){
    return x.id<y.id;
}
ll sum[maxn],num[maxn];
int Ans;
int lowbit(int x){
    return x&(-x);
}
void add(int x,ll val){
    while(x<=n){
        num[x]++;
        sum[x]+=val;
        x+=lowbit(x);
    }
}
ll ask(int x){
    ll res=0;
    while(x){
        Ans+=num[x];
        res+=sum[x];
        x-=lowbit(x); 
    }
    return res;
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        memset(num,0,sizeof(num));
        memset(sum,0,sizeof(sum));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i].val);
            a[i].id=i;
        }
        sort(a+1,a+1+n,cmp1);
        for(int i=1;i<=n;i++)
            a[i].rk=i;
        sort(a+1,a+1+n,cmp2);
        ll tmp=0;
        for(int i=1;i<=n;i++){
            tmp+=a[i].val;
            if(tmp<=m)ans[i]=0;
            else{
                ll x=tmp-m;
                int l=0,r=n;
                Ans=0;
                ll s1=ask(n); ll Ans1=Ans;
                while(l<=r){
                    int mid=l+r>>1;
                    Ans=0;
                    ll s2=ask(mid); ll Ans2=Ans;
                    if(s1-s2>=x){
                        ans[i]=Ans1-Ans2;
                        l=mid+1; 
                    }else r=mid-1;
                }
            }
        //    cout<<a[i].rk<<" "<<a[i].val<<endl;
            add(a[i].rk,a[i].val);
        }
        for(int i=1;i<=n;i++)
            printf("%d ",ans[i]);
        printf("\n");
    }
    return 0;
}

 

posted @ 2019-07-30 15:01  两点够吗  阅读(264)  评论(0编辑  收藏  举报