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; }