技能升级
技能升级
小蓝最近正在玩一款 $RPG$ 游戏。
他的角色一共有 $N$ 个可以加攻击力的技能。
其中第 $i$ 个技能首次升级可以提升 $A_i$ 点攻击力,以后每次升级增加的点数都会减少 $B_i$。
$\left\lceil \frac{A_i}{B_i} \right\rceil$(上取整)次之后,再升级该技能将不会改变攻击力。
现在小蓝可以总计升级 $M$ 次技能,他可以任意选择升级的技能和次数。
请你计算小蓝最多可以提高多少点攻击力?
输入格式
输入第一行包含两个整数 $N$ 和 $M$。
以下 $N$ 行每行包含两个整数 $A_i$ 和 $B_i$。
输出格式
输出一行包含一个整数表示答案。
数据范围
对于 $40\%$ 的评测用例,$1 \leq N,M \leq 1000$;
对于 $60\%$ 的评测用例,$1 \leq N \leq {10}^{4}$,$1 \leq M \leq {10}^{7}$;
对于所有评测用例,$1 \leq N \leq {10}^{5}$,$1 \leq M \leq 2 \times {10}^{9}$,$1 \leq A_i,B_i \leq {10}^{6}$。
输入样例:
3 6 10 5 9 2 8 1
输出样例:
47
解题思路
为了得到最大的总和,那么应该取前$m$最大的数,因此很容易想到多路归并,每次取这$n$组数中最大的那一个,具体做法是把先把这$n$个元素压到优先队列(大根堆),每次取堆顶元素进行累加,然后再把这个数减去对应的$b_i$再压入优先队列中(如果变化后小于等于$0$就不再压入),重复该步骤直到队列为空或进行了$m$次,这种做法的时间复杂度为$O(m \cdot \log{n})$,会超时。
为了求出前$m$个最大值,现在换一种思路,考虑从大到小数排名为第$m$位的数值是多少,假设数值为$x$。可以通过二分数值来找到这个数$x$,在所有技能点所能构成的数中,可以发现$\geq x$的数的个数$\geq m$个(可能有多个$x$重复),而$\geq x + 1$的数的个数$< m$个。对于小于等于$x$的数值$t$,可以发现$\geq t$的数的个数$\geq m$个;对于大于$x$的数值$t$,可以发现$\geq t$的数的个数$< m$个。因此具有二段性,可以进行二分。当二分到$\text{mid}$,在$\text{check}$的时候只需要遍历所有数,看一下以$a_i$为首项,$-{b_i}$为公差的等差数列中有多少项时大于等于$\text{mid}$的,如果$a_i \geq \text{mid}$,那么就有$\left\lfloor {\frac{a_i - \text{mid}}{b_i}} \right\rfloor + 1$项。因此时间复杂度为$O(n \log{{10}^{6}})$。
求出来第$m$个数$x$后,接下来就要求前$m$个数的总和,也就是把所有大于等于$x$的数求和。方法是遍历一遍所有数,如果有$a_i \geq x$,然后求$t = \left\lfloor {\frac{a_i - \text{mid}}{b_i}} \right\rfloor + 1$,那么根据等差数列求和公式$S_n = \frac{n(a_1 + a_n)}{2} = n \cdot a_1 + \frac{n(n-1)}{2} \cdot d$,把所有大于等于$x$的项(即前$t$项)进行累加。同时还要注意到等于$x$数可能有多个,而在累加的时候会把所有等于$x$的也累加,因此还需要把多余的$x$减掉,方法是在遍历的时候开个变量$\text{cnt}$记录一共累加了多少项(也就是累加$t$),最后再对答案减去$(\text{cnt} - m) * x$即可。
注意由于这$n$组数可能无法构成$m$个以上的数,因此可能无法取到第$m$个数,因此二分的左边界设为$0$,当出现这种情况时二分得到的结果必然是$0$,可以发现按照上面累加总和的方法得到的结果还是对的(也就是把全部的数累加)。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const int N = 1e5 + 10; 7 8 int n, m; 9 int a[N], b[N]; 10 11 bool check(int mid) { 12 LL cnt = 0; 13 for (int i = 0; i < n; i++) { 14 if (a[i] >= mid) cnt += (a[i] - mid) / b[i] + 1; // 求有多少个大于等于mid的项 15 } 16 return cnt >= m; 17 } 18 19 int main() { 20 scanf("%d %d", &n, &m); 21 for (int i = 0; i < n; i++) { 22 scanf("%d %d", a + i, b + i); 23 } 24 int l = 0, r = 1e6; 25 while (l < r) { 26 int mid = l + r + 1 >> 1; 27 if (check(mid)) l = mid; 28 else r = mid - 1; 29 } 30 LL ret = 0, cnt = 0; 31 for (int i = 0; i < n; i++) { 32 if (a[i] >= l) { 33 LL t = (a[i] - l) / b[i] + 1; // 求有多少项大于等于第m大数x 34 cnt += t; // 累加了多少项 35 ret += t * a[i] - t * (t - 1) / 2 * b[i]; // 求前t项的总和 36 } 37 } 38 printf("%lld", ret - (cnt - m) * l); // 减去多累加的x 39 40 return 0; 41 }
参考资料
AcWing 4656. 技能升级(寒假每日一题2023):https://www.acwing.com/video/4594/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17040773.html