Acwing-4656-技能升级

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

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

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

⌈AiBi⌉(上取整)次之后,再升级该技能将不会改变攻击力。

现在小蓝可以总计升级 M 次技能,他可以任意选择升级的技能和次数。

请你计算小蓝最多可以提高多少点攻击力?

输入格式

输入第一行包含两个整数 N 和 M。

以下 N 行每行包含两个整数 Ai 和 Bi。

输出格式

输出一行包含一个整数表示答案。

数据范围

对于 40% 的评测用例,1≤N,M≤1000;
对于 60% 的评测用例,1≤N≤10^4,1≤M≤107;
对于所有评测用例 1≤N≤10^5,1≤M≤2×10^9,1≤Ai,Bi≤10^6。

输入样例:

3 6
10 5
9 2
8 1

输出样例:

47

思路分析:

        该题的意思是我们手中有m次随意选择技能加点的机会,有n个技能,可以对任意的技能加点,但是每个技能接受过一次加点后(加入第一次加点为a[i]),下次再对其加点则会减少b[i]点,即下次加点只能加a[i] - b[i],逐次累减。求出最多可以加多少点。

        按常规思路来想,我们首先肯定是升级当前可以增加技能点数最多的那一个技能,如第一次加点,一定是对所有技能从大到小排序后,第一个技能就是最大的。但是每加一次技能,会影响刚刚排好的序列,因为会减少当前技能b[i],同时每个技能减少的那个b[i]也是不确定的,所以暴力去做(即每选一次对所有技能排序一次再选下一次)时间肯定过不去。

        因为我们不确定的只有前m个数该选哪m个是最大的,这里我们可以用二分去枚举第m个数是多少,拿到手中的这个mid值根据等差数列公式去求比mid大的数有多少个,如果超过了m个,说明我们取的这个mid值太小了,l = mid扩大二分区间继续找,最终找到的mid就是第m个要选的那个数的值。

        对于上面的check函数中,等差数列公式 a[i] - (n -1) * b[i]表示第i个技能升级i次后如果再升级所能够加的点数,它要>=mid(先于m次所升级的技能,即比第m次升级的这个技能所加点更多的技能需要优先升级),移项化简得n <= (a[i] - x) / b[i] + 1;这里的n即表示第i个技能大于mid的情况下(第m次技能升级前升级的可以升级的技能)最多能够升级多少次,然后把n个技能都运算一遍加起来得到res,返回是否res >= x,即得到在当前二分的第m大的要加点值得情况下,满足在第m次升级技能前,是否已经升级了m次以上,如果是,说明二分的这个第m大的值小了,需要转移到大区间继续二分查找。

        这里还有一点优化和一点细节,优化就是寻找n的时候,如果第一项也就是a[i]初始值就小于mid,则不用进行下面的计算,因为该技能压根就不被我们放在眼里,加点太少自始至终不用来升级;另一点细节就是,当我们二分完找到第m次升级所加点的值,这个值可能会与另外某个技能或者多个技能的当前状态所加点的值相等,这就要在后面的计算中将这些相等的技能所一同加的点删掉,保证正确答案。

        最后就是对每个技能单独考虑,计算最大加点值之和是多少。假设二分答案是r,拿到这个r用上述同样的公式n <= (a[i] - x) / b[i] + 1,计算出这个n就表示对于每个技能选了多少次,最大加点值之和res += 每个技能加点之和,用等差数列求和公式 (a[1] + a[n]) * n / 2求得,同时用cnt += n记录下所有升级技能的次数,如果cnt == m说明没有上面所说的那一细节情况,如果cnt > m则需要在最终的结果res中减去多加的技能点。最终返回的就是要求的最大加点之和。

代码如下:

import java.util.*;

class Main{
    static final int N = 100010;
    static int n, m;
    static int[] a = new int[N], b = new int[N];
    static boolean check(int x){
        long res = 0;
        for(int i = 0; i < n; i++){
            if(a[i] >= x) res += (a[i] - x) / b[i] + 1;
        }
        return res >= m;
    }
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt(); m = sc.nextInt();
        for(int i = 0; i < n; i++){
            a[i] = sc.nextInt();
            b[i] = sc.nextInt();
        }
        int l = 0, r = (int)1e6;
        while(l < r){
            int mid = l + r + 1 >> 1;
            if(check(mid)) l = mid;
            else r = mid - 1;
        }
        long cnt = 0, res = 0;
        for(int i = 0; i < n; i++){
            if(a[i] >= l){
                long num = (long)(a[i] - l) / b[i] + 1;
                cnt += num;
                long end = (long)(a[i] - (num - 1) * b[i]);
                res += ((long)a[i] + end) * num / 2;
            }
        }
        System.out.println(res - (long)(cnt - m) * l);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值