题目描述
在一个游戏中,tokitsukaze需要在n个士兵中选出一些士兵组成一个团去打副本。
第i个士兵的战力为v[i],团的战力是团内所有士兵的战力之和。
但是这些士兵有特殊的要求:如果选了第i个士兵,这个士兵希望团的人数不超过s[i]。(如果不选第i个士兵,就没有这个限制。)
tokitsukaze想知道,团的战力最大为多少。
输入描述:
第一行包含一个正整数n(1≤n≤10^5)。
接下来n行,每行包括2个正整数v,s(1≤v≤10^9,1≤s≤n)。
输出描述:
输出一个正整数,表示团的最大战力。
示例1
输入
2
1 2
2 2
输出
3
示例2
输入
3
1 3
2 3
100 1
输出
100
刚学完权值线段树就碰到这么一道题。。。
思路
当我们选择3个士兵的时候,所有限制条件大于等于3的士兵都可以选择,那么我们肯定选择3个权值最大的。
所以可以枚举选取士兵的个数,求前k大士兵的权值和。(求前k个,就想到权值线段树了)
本来用的multiset,超时了,就写了线段树。
首先将数据离散化,按照限制条件从大到小排序,维护一个权值线段树,每个节点维护当前区间的数出现的次数以及总和,然后将限制条件相同的士兵的权值更新到线段树中,查询当前树中的前k大的和,注意可能有多个相同的权值,所以写查询的时候注意一下。
代码
#include<bits/stdc++.h>
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<set>
using namespace std;
const int N=1e5+10;
typedef long long ll;
struct note//保存士兵信息
{
ll val,num;
} arr[N];
struct T
{
ll l,r,size,pre;
} node[N*10];//线段树
bool cmp(note a,note b)
{
return a.num>b.num;
}
vector<ll>v;
ll getid(ll x)//离散化
{
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void update(ll rt,ll l,ll r,ll val,ll real)//更新,val是离散化后的值,real是原来的值
{
node[rt].pre+=real,node[rt].size++;
if(l==r) return ;
ll mid=(l+r)/2;
if(val<=mid) update(rt*2,l,mid,val,real);
else update(rt*2+1,mid+1,r,val,real);
}
ll query(ll rt,ll l,ll r,ll k)//查询
{
if(node[rt].size<=k) return node[rt].pre;
if(node[rt].size>k&&l==r) return k*(node[rt].pre/node[rt].size);//多个权值相同的时候
ll mid=(l+r)/2;
ll len=node[rt*2+1].size;//优先向右查询
if(k<=len) return query(rt*2+1,mid+1,r,k);
else return node[rt*2+1].pre+query(rt*2,l,mid,k-len);
}
int main()
{
ll n;
scanf("%lld",&n);
for(ll i=1; i<=n; i++) scanf("%lld%lld",&arr[i].val,&arr[i].num),v.push_back(arr[i].val);
sort(arr+1,arr+1+n,cmp),sort(v.begin(),v.end()),v.erase(unique(v.begin(),v.end()),v.end());
ll size=v.size(),ans=0;
for(ll i=1; i<=n; i++)
{
ll now=arr[i].num;//当前选取士兵的个数
ll j=i;
while(j<=n&&arr[j].num==now)//相同限制条件全部更新
update(1,1,size,getid(arr[j].val),arr[j].val),j++;
i=j-1;
ans=max(ans,query(1,1,size,now));
}
printf("%lld\n",ans);
return 0;
}