题意:
给了 $N$ 个数 $W_k$ , 对于每个 $1 \le i \le n$, 在 $i$ 以前不包括 $i$ 的所有 $W$ 里选取最少的一些 $W$ 将其变为 $0$ , 使得 从 $1$ 到 $i$ 的和不大于 $m$ 。要求对于每个 $i$ 输出最小的数目。
题解:
做的时候傻了,一直用set乱搞,搞来搞去总是tle,主要是用set维护真的会被卡。。用平衡树板子又有点大材小用,于是就米勒很久。
所以说遇到一个简单题很久没出的话,可能就得考虑全盘否定直接从来了。。。
如果一开始就没有被赵老师带着想set,而是直奔线段树去的话,就很好搞了,直接把问题转化为在前 i-1 个数里找出最少的数字使得之和大于 $pre[i]-m$ ,其中 $pre[i]$ 是前缀和。这样的话直接用权值线段树来做,每个节点存两个域,一个是数字的个数,一个是数字的和。。然后query的时候利用线段树天然的二分性质来搞就行了。。。
这题其实动机真的不难,就是因为不用数据结构的话每次要排序,然后权值线段树天生有排序的特性在里面。。。所以遇事不决还是先考虑线段树。。。不能一开始就想骚操作emmmmm
#include<iostream> #include<algorithm> #include<set> #include<vector> using namespace std; typedef long long ll; const int maxn = 2e5; ll a[maxn],has[maxn],cnt,n,m; int Hash(ll x){ return lower_bound(has+1,has+1+cnt,x)-has; } ll Ceil(ll a,ll b){ return (a+b-1)/b; } ll sum[maxn<<2],num[maxn<<2]; void build(int o,int l,int r){ if(l==r){ sum[o]=num[o]=0; return ; } int mid=(l+r)>>1; build(o<<1,l,mid); build(o<<1|1,mid+1,r); sum[o]=num[o]=0; } int p;ll v; void update(int o,int l,int r){ if(l==r){ sum[o]+=has[p]; num[o]++; return ; } int mid=(l+r)>>1; if(p<=mid) update(o<<1,l,mid); else update(o<<1|1,mid+1,r); sum[o]=sum[o<<1]+sum[o<<1|1]; num[o]=num[o<<1]+num[o<<1|1]; } ll an,cp,unit; void query(int o,int l,int r){ if(l==r){ if(num[o]==0) return ; unit=sum[o]/num[o]; an+=Ceil(cp,unit); return ; } int mid=(l+r)>>1; if(sum[o<<1|1]>=cp) query(o<<1|1,mid+1,r); else { cp-=sum[o<<1|1]; an+=num[o<<1|1]; query(o<<1,l,mid); } } ll pre; void init(){ build(1,1,cnt); pre=0; } int main() { int T; scanf("%d",&T); while (T--){ scanf("%lld%lld",&n,&m); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); has[i]=a[i]; } sort(has+1,has+1+n); cnt=1; for(int i=2;i<=n;i++) if(has[cnt]!=has[i]) has[++cnt]=has[i]; init(); for(int i=1;i<=n;i++){ pre+=a[i]; cp=pre-m; an=0; query(1,1,cnt); printf("%d ",an); p=Hash(a[i]); update(1,1,cnt); } puts(""); } return 0; }