技能升级

技能升级

小蓝最近正在玩一款 $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/

posted @ 2023-01-10 17:07  onlyblues  阅读(50)  评论(0编辑  收藏  举报
Web Analytics