技能升级

Posted onlyblues

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了技能升级相关的知识,希望对你有一定的参考价值。

技能升级

小蓝最近正在玩一款 $RPG$ 游戏。

他的角色一共有 $N$ 个可以加攻击力的技能。

其中第 $i$ 个技能首次升级可以提升 $A_i$ 点攻击力,以后每次升级增加的点数都会减少 $B_i$。

$\\left\\lceil \\fracA_iB_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 \\logn)$,会超时。

  为了求出前$m$个最大值,现在换一种思路,考虑从大到小数排名为第$m$位的数值是多少,假设数值为$x$。可以通过二分数值来找到这个数$x$,在所有技能点所能构成的数中,可以发现$\\geq x$的数的个数$\\geq m$个(可能有多个$x$重复),而$\\geq x + 1$的数的个数$< m$个。对于小于等于$x$的数值$t$,可以发现$\\geq t$的数的个数$\\geq m$个;对于大于$x$的数值$t$,可以发现$\\geq t$的数的个数$< m$个。因此具有二段性,可以进行二分。当二分到$\\textmid$,在$\\textcheck$的时候只需要遍历所有数,看一下以$a_i$为首项,$-b_i$为公差的等差数列中有多少项时大于等于$\\textmid$的,如果$a_i \\geq \\textmid$,那么就有$\\left\\lfloor \\fraca_i - \\textmidb_i \\right\\rfloor + 1$项。因此时间复杂度为$O(n \\log10^6)$。

  求出来第$m$个数$x$后,接下来就要求前$m$个数的总和,也就是把所有大于等于$x$的数求和。方法是遍历一遍所有数,如果有$a_i \\geq x$,然后求$t = \\left\\lfloor \\fraca_i - \\textmidb_i \\right\\rfloor + 1$,那么根据等差数列求和公式$S_n = \\fracn(a_1 + a_n)2 = n \\cdot a_i + \\fracn(n-1)2 \\cdot d$,把所有大于等于$x$的项(即前$t$项)进行累加。同时还要注意到等于$x$数可能有多个,而在累加的时候会把所有等于$x$的也累加,因此还需要把多余的$x$减掉,方法是在遍历的时候开个变量$\\textcnt$记录一共累加了多少项(也就是累加$t$),最后再对答案减去$(\\textcnt - m) * x$即可。

  注意由于这$n$组数可能无法构成$m$个以上的数,因此可能无法取到第$m$个数,因此二分的左边界设为$0$,当出现这种情况时二分得到的结果必然是$0$,可以发现按照上面累加总和的方法得到的结果还是对的(也就是把全部的数累加)。

 #include <bits/stdc++.h>
 using namespace std;
 
 typedef long long LL;
 
 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) cnt += (a[i] - mid) / b[i] + 1;    // 求有多少个大于等于mid的项
     
     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;
         if (check(mid)) l = mid;
         else r = mid - 1;
     
     LL ret = 0, cnt = 0;
     for (int i = 0; i < n; i++) 
         if (a[i] >= l) 
             LL t = (a[i] - l) / b[i] + 1;   // 求有多少项大于等于第m大数x
             cnt += t;   // 累加了多少项
             ret += t * a[i] - t * (t - 1) / 2 * b[i];   // 求前t项的总和
         
     
     printf("%lld", ret - (cnt - m) * l);    // 减去多累加的x
     
     return 0;
 

 

参考资料

  AcWing 4656. 技能升级(寒假每日一题2023):https://www.acwing.com/video/4594/

以上是关于技能升级的主要内容,如果未能解决你的问题,请参考以下文章

为了升级到架构师,程序员无需过度关注哪些技能?哪些技能不可缺?

技能get,React的优雅升级!

第十三届蓝桥杯省赛 C++ C 组 I 题Python B 组 H题——技能升级(AC)

Kotlin 威胁Python 逆袭,2018 年程序员需要升级哪些技能?

WF4.0——升级技能:泛型应用

黑苹果必备技能之一:升级OC(open core)引导