给定一些物品(或任务),每个物品有一个初始值 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