题目:
样例输入:
3 6
10 5
9 2
8 1
样例输出:
47
分析:如果这道题目模拟来做的话是非常好做的,因为我们每次都会选择当前可提升攻击力最多的技能进行升级,显然这样是最优的。那么这也就意味着我们每次选择的技能提升的攻击力是非递增的,于是我们显然可以对最后一次技能提升的攻击力进行二分,但是需要注意一点,就是假如我们最优情况下最后一次提升的攻击力是x,那么这并不代表我们下一次提升的攻击力最多是x-1,因为可能还存在使得提升攻击力也为x的技能,所以这一点是我们需要考虑的。但是我们可以确定的是提升攻击力大于x的技能都已经使用完了,所以我们可以先把所有提升攻击力大于x的技能所提升的攻击力算出来,并计算出使用技能的次数,那么剩下的就是提升攻击力等于x的技能了。用m-已经使用技能的次数,然后乘以x即可得到我们使用提升攻击力为x的技能所提升的总攻击力。可能这个时候会有小伙伴有疑问:有没有可能没有提升攻击力为x的技能呢?其实这是不可能的,因为我们是对提升的攻击力进行二分,所以所得结果一定是最优的,假如我们二分得到的结果是x,那么可以满足大于x的技能全部被使用光,而且大于等于x的技能一定会有剩余,这是我们的二分性质决定的,所以直接对最低提升攻击力点数进行二分即可。细节见代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+10;
typedef long long ll;
ll a[N],b[N];
int n,m;
bool check(ll x)
{
ll cnt=0;
x++;//至少要等于x+1的攻击力才能使用该技能
for(int i=1;i<=n;i++)
{
if(a[i]<x) continue;
ll times=(a[i]-x)/b[i]+1;
cnt+=times;
if(cnt>m) return false;//剪枝
}
return true;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%lld%lld",&a[i],&b[i]);
ll l=0,r=1e6;
while(l<r)
{
ll mid=l+r>>1;//本次增加的攻击力是要大于mid的
if(check(mid))
r=mid;
else l=mid+1;
}
ll ans=0;
int cnt=0;//使用增加攻击力大于l的技能次数
for(int i=1;i<=n;i++)
{
if(a[i]<=l) continue;
ll times=(a[i]-(l+1))/b[i]+1;
cnt+=times;
ans+=(a[i]+a[i]-(times-1)*b[i])*times/2;
}
ans+=(m-cnt)*l;
printf("%lld\n",ans);
return 0;
}