来补题辣~
这题很简单,我们来判断下一个选择的序列是否合法的充要条件是什么:
合法是要满足对于所有的选择进去的i,都要满足s[i]>=总人数
等价于min(s[i])>=总人数
所以,我们可以直接枚举这个min(s[i]),然后问题就转化成了,在一定范围内,选s[i]-1个人的最大战力,那么我们选择前s[i]-1个人即可(需要注意的是,可能没有s[i]-1个人,这个时候范围内全选即可)
所以,做法就是,我们先把所有士兵按s[i]从小到大排序,然后统计出i+1-n中,前s[i]-1大的数的和是多少,注意到,如果我们倒着枚举的话,可以减少删除的操作,所以我们倒着枚举i,然后,用一颗权值线段树维护计算即可
具体是,对每个节点我们维护当前节点有几个值,以及这些值的和是多少,然后,我们判断下:(假设查询前k大数的和)
若当前节点所具有的值的个数小于等于k,返回这些值的和即可,若访问到了最底层(即l=r时),我们输出k*v(v表当前节点对应的点的值),否则,我们类比kth:
若右儿子节点数量大于等于k,那么,我们向右儿子找
否则,我们加上右儿子所有节点的值,然后向左儿子找前(k-右儿子的值的个数)大的值的和即可
代码:
#include<bits/stdc++.h> using namespace std; #define int long long const int N=1e6+1; struct node{ int v,s,val; }t[N]; int W[N<<2],S[N<<2]; int b[N],s[N]; inline int find(int now,int l,int r,int k){ if(W[now]<=k){ return S[now]; } if(l==r){ return s[l]*k; } int mid=(l+r)>>1; if(W[now<<1|1]>=k){ return find(now<<1|1,mid+1,r,k); } return S[now<<1|1]+find(now<<1,l,mid,k-W[now<<1|1]); } inline void insert(int now,int l,int r,int x,int y){ ++W[now];S[now]+=y; if(l==r){ return; } int mid=(l+r)>>1; if(x<=mid){ insert(now<<1,l,mid,x,y); }else{ insert(now<<1|1,mid+1,r,x,y); } } inline bool kkk(node x,node y){ return x.s<y.s; } signed main(){ int n; scanf("%lld",&n); for(int i=1;i<=n;++i){ scanf("%lld%lld",&t[i].v,&t[i].s); b[i]=t[i].v; } sort(b+1,b+n+1); int m=unique(b+1,b+n+1)-b-1; for(int i=1;i<=n;++i){ t[i].val=lower_bound(b+1,b+m+1,t[i].v)-b; s[t[i].val]=t[i].v; } sort(t+1,t+n+1,kkk); int ans=0; for(int i=n;i;--i){//枚举最小的s int k=t[i].s-1; int res=t[i].v; //从i+1-n中选前k大的v if(k){ res+=find(1,1,m,k); } insert(1,1,m,t[i].val,t[i].v); ans=max(ans,res); } printf("%lld",ans); return 0; }