技能升级
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/
以上是关于技能升级的主要内容,如果未能解决你的问题,请参考以下文章
为了升级到架构师,程序员无需过度关注哪些技能?哪些技能不可缺?
第十三届蓝桥杯省赛 C++ C 组 I 题Python B 组 H题——技能升级(AC)