技能升级问题

给定一些物品(或任务),每个物品有一个初始值 a[i] 和每次减少的值 b[i],每次操作你都可以选择当前值最大的物品来获得其价值,然后它的值减少 b[i]。这个过程重复进行 m 次,目标是求出在 m 次操作后你能获得的最大总价值。

 

C++ 容器类 <priority_queue> | 菜鸟教程 

#include <bits/stdc++.h> 
using namespace std;
typedef long long ll; // 使用 ll 代替 long long

// 定义一个结构体 node,表示一个技能
struct node
{
  ll value;  // 当前技能的攻击力
  int idx;   // 第idx个技能
};

// 定义优先队列的比较函数,使其变为最大堆
struct cmp {
  bool operator()(const node& a, const node& b) const
  {
    return a.value < b.value; // 值大的优先,形成最大堆
  }
};

int main()
{
  ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); // 提高输入输出效率

  ll n, m;
  cin >> n >> m; // 输入技能数量 n 和操作次数 m

  vector<int> a(n+1), b(n+1); // a[i] 表示第 i 个技能初始攻击力,b[i] 表示每次的减少值

  // 优先队列(最大堆),按物品当前值从大到小排序
  priority_queue<node, vector<node>, cmp> pq;

  // 读入每个技能的 a[i] 和 b[i],并将初始值压入堆中
  for(ll i = 1; i <= n; i++)
  {
    cin >> a[i] >> b[i];
    pq.push({a[i], i});
  }

  ll sum = 0; // 记录总获得价值

  // 进行 m 次操作
  while(m--)
  {
    node maxtop = pq.top(); // 取出当前攻击力最大的技能
    pq.pop();

    sum += maxtop.value; // 累加攻击力

    int topidx = maxtop.idx; // 获取该技能的索引

    ll next_value = maxtop.value - b[topidx]; // 升级一次后技能的攻击力

    if(next_value > 0)
    {
      // 如果升级一次后技能的攻击力仍然大于 0,就重新加入堆中
      pq.push({next_value, topidx});
    }
  }

  cout << sum << '\n'; // 输出增大的总技能
  return 0;
}

 只能过部分测试点,后面会超时。

//本题考查二分算法,核心是找到第M大的数
//给出的n组数据本质上是n个递减的等差数列,首项为A[i],公差为B[i]
//可以将这n个等差数列展开,发现核心就是从这些等差数列中找到M个最大的数并累加
//因此可以用二分算法找到数列中第M大的数x,然后遍历所有的等差数列,将所有大于等于x的数累加即可
//附:本题若暴力求解可以使用优先队列,不断令当前最大数出队并累加,递减后再次入队
//如果使用暴力解法可以较快地通过5个测试点,但后5个会超时 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;

ll n, m;
ll A[N],B[N];

bool check(ll x)//验证第M大的数能否是x(求等差数列中有几个数大于等于x)
{
    ll cnt = 0;
    for(int i = 1; i <= n; i++)
    {
        if(A[i] < x)continue;
        int k = (A[i] - x) / B[i];
        cnt += k + 1;
    }
    if(cnt >= m)return true;//大于等于x的数的数量不少于m个,返回true 
    else return false;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>A[i]>>B[i];
    
    int left=0,right=1e6+10,x=0;//x为第M大的数 
    while(left<=right)//开始二分 
    {
        int mid = (left + right) >> 1;
        if(check(mid))//等差数列中不小于mid的元素的数量大于等于m,说明mid选小了 
        {
            x=mid;//记录之 
            left=mid+1;//扩大左边界 
        }
        else right=mid-1;//否则缩小右边界 
    }
    //经过以上循环,第M大数为x,接下来只需要求解数列中所有大于等于x的数的和即可 
    
    //此处要注意一个细节,那就是数列中可能存在多个等于x的数
    //因此不能直接累加数列中所有大于等于x的数,否则可能出现多次累加等于x的数,总次数超过m 
    //比较好的做法是只累加大于x的数,然后计算还剩下几个等于x的数没有累加,累加上即可 
    ll cnt=0,sum=0;//大于x的数字个数为cnt,数字之和为sum
    for(int i=1;i<=n;i++)//遍历每个等差数列 
    {
        if(A[i]<x)continue;//第一个数就小于x,后面不可能大于x,直接跳过 
        int k=(A[i]-x)/B[i];//计算该数列中有几个数大于x 
        if(k*B[i] != (A[i] - x))k++;//k向上取整 
        sum=sum+(A[i]+A[i]-(k-1)*B[i])*k/2;//等差数列求和公式 
        cnt=cnt+k;//记录已经计算了几个数 
    }
    sum+=(m-cnt)*x;//最终要累加m个数,已经累加了cnt个比x大的数,还应累加m-cnt个等于x的数(细节) 
    cout<<sum<<endl;
    return 0;
}

难理解

2129

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值