链接:4656. 技能升级 - AcWing 题库
题目描述
小蓝最近正在玩一款 R P G RPG RPG 游戏。
他的角色一共有 N N N 个可以加攻击力的技能。
其中第 i i i 个技能首次升级可以提升 A i A_i Ai 点攻击力,以后每次升级增加的点数都会减少 B i B_i Bi。
⌈ A i B i ⌉ ⌈\frac{A_i}{B_i}⌉ ⌈BiAi⌉(上取整)次之后,再升级该技能将不会改变攻击力。
现在小蓝可以总计升级 M M M 次技能,他可以任意选择升级的技能和次数。
请你计算小蓝最多可以提高多少点攻击力?
输入格式:
输入第一行包含两个整数 N N N 和 M M M。
以下 N N N 行每行包含两个整数 A i A_i Ai 和 B i B_i Bi。
输出格式:
输出一行包含一个整数表示答案。
数据范围:
对于 40% 的评测用例,
1
≤
N
,
M
≤
1000
1≤N,M≤1000
1≤N,M≤1000;
对于 60% 的评测用例,
1
≤
N
≤
1
0
4
1≤N≤10^4
1≤N≤104,
1
≤
M
≤
1
0
7
1≤M≤10^7
1≤M≤107;
对于所有评测用例,
1
≤
N
≤
1
0
5
1≤N≤10^5
1≤N≤105,
1
≤
M
≤
2
×
1
0
9
1≤M≤2×10^9
1≤M≤2×109,
1
≤
A
i
,
B
i
≤
1
0
6
1≤A_i,B_i≤10^6
1≤Ai,Bi≤106。
输入样例:
3 6
10 5
9 2
8 1
输出样例:
47
思路
先暴力分析题意:
每个技能升级的次数是有限的,每次升级要取攻击力最高的技能,即可以将每个技能每次升级后的攻击力都列出来,得到该技能的等差数列,再混合在一起从大到小排序,取前 m
个,得到最优结果。
但是数据较大,需要考虑优化。
用贪心的思想来想:
每次取最大的,会发现存在一个数
x
x
x 作为分界点,使得
≥
x
\ge x
≥x 的数的个数
≥
m
\ge m
≥m 个(因为这个数
x
x
x 可能不止一个),因此我们二分答案找到这个
x
x
x 。二分的时候左边界取 0
,因为有可能将所有的数都取完后都没有 m
个,左边界取 1
的话可能会产生错误。
然后再从头开始遍历每个技能, x x x 就相当于是每个等差数列的末项,所以我们要用等差数列求和公式求出从首项到末项的和。
最后考虑
x
x
x 可能会有多个,如果全部取就会超过 m
个,为解决这个问题,我们可以先全部取上,最后再减去多余的
x
x
x ,于是得到最终的最优解。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], b[N];
bool check(int mid)
{
ll cnt = 0; //统计到分界点为止,该技能的升级次数
for (int i = 0; i < n; i++){
if (a[i] >= mid) //mid相当于等差数列的末项,b[i]为公差
cnt += (a[i] - mid) / b[i] + 1;
}
return cnt >= m;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) scanf("%d%d", &a[i], &b[i]);
//二分答案
int l = 0, r = 1e6;
while (l < r){
int mid = l + r + 1 >> 1;
//因为二分的范围是有序的,如果超过了m个数,最优原则下要从大到小选,则范围往大的方向缩小,反之则往小的范围继续选
if (check(mid)) l = mid;
else r = mid - 1;
}
ll res = 0, cnt = 0;
for (int i = 0; i < n; i++){
if (a[i] >= r) //二分出来的边界点x=l=r,写r的话不容易看错
{
int c = (a[i] - r) / b[i] + 1; //项数
int end = a[i] - (c - 1) * b[i]; //末项
res += (ll)(a[i] + end) * c / 2; //求和公式,a[i]为首项
cnt += c; //统计每次所选的数的个数
}
}
//最后要减去多余的x
printf("%lld\n", res - r * (cnt - m));
return 0;
}