算法学习笔记——二分、前缀和、贪心 2024.5.21

这道题是蓝桥上的一道题目(Lanqiao OJ 3875)

问题描述

小蓝作为班级里的体育健将,他被安排在校运会时参加 𝑛 个体育项目,第 𝑖 个体育项目参赛需要耗时 𝑎𝑖​ 分钟,赛后休息需要 𝑏𝑖​ 分钟(这意味着当他参加完这场比赛后需要经过 𝑏𝑖 分钟才能再次参加其他项目)。

由于小蓝实在太强了,无论参加任何比赛他都可以拿到金牌,但他能够参加校运会的时间有限,最多只能参加 𝑘 分钟,请问他最多能拿多少块金牌?

输入格式

第一行包含两个整数 𝑛 和 𝑘,表示体育项目数量和小蓝的可参加校运会时间。

第二行包含 𝑛 个整数 a1​,a2​,⋯,an​,表示每个项目需要的参赛时间。

第三行包含 𝑛 个整数 b1​,b2​,⋯,bn​,表示每个项目后面需要的休息时间。

数据范围保证:1≤𝑛≤2e5,1≤𝑎𝑖,𝑏𝑖,𝑘≤1e8。

输出格式

输出一个整数,表示在 𝑘​k​ 分钟内小蓝最多可以拿到几个金牌。

输入样例

3 10
3 4 5
2 4 2

输出样例

2

说明

样例中小蓝可以先参加比赛 1,然后再参加比赛 3。虽然参加比赛 3 后还需要休息 2 分钟,但这不影响他在 10 分钟内参加了两个项目拿下两块金牌。

解题思路:

一. 暴力贪心

开始时采用了暴力贪心策略,但是有数据过不了,姑且先介绍一下暴力贪心:

首先对所有项目的总时间计算并进行从小到大的排序,再从总时间最小的开始遍历所有项目。如果该项目的比赛时间小于等于目前所剩的时间,那么就让答案+1,再从目前所剩时间里减去该项目的比赛时间和休息时间的总和,随后进行下一次循环。如果当前所剩时间<=0,那么就终止循环并输出答案。

以下是贪心的代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 1;
int a[N], b[N], c[N];
int main()
{
  int n, k;cin >> n >> k;
  int num = 0;
  vector<pair<int,int>> vec; 
  for(int i = 1; i <= n; i++) cin >> a[i];
  for(int i = 1; i <= n; i++) 
  {
    cin >> b[i];
    vec.push_back(make_pair(a[i]+b[i] ,i));
  }
  sort(vec.begin(),vec.end());

  for(int i = 0; i < n && k > 0; i++)
  {
    int idx = vec[i].second;
    if(k-a[idx]>=0)
    {
      k = k - vec[i].first;
      num ++;
    }
  }

  cout << num << '\n';
  return 0;
}

 然而,暴力贪心存在一个问题,就是它是由局部最优解推得总体最优解的,对于暴力贪心这种贪心策略,某些情况下的局部最优解并不能得到总体最优解,以下数据就是一个例子:

2 10
2 5
5 3

如果用暴力贪心,得到的答案是1,但是正确答案显而易见是2,因而我们需要换一种思路,或者进行贪心优化

二. 前缀和、二分答案

本方法是评论区里一位大佬zccccc提供的思路,本文借鉴一下。此方法的本质还是枚举,同时采取了各种优化:二分法——优化枚举,前缀和——优化求区间和

由于我们在计算总时间时,唯一不需要计算休息时间的就是最后一个项目,它也是最特殊的,所以我们把它单拎出来进行枚举。

总体思路:

(注:参赛时间et,休息时间br)

1.枚举参加的最后一个项目item[i],算出除去该项目后的剩余时间temp - item[i].et

2.按总时间(参赛+休息)排序,并求出其前缀和数组s,s[i]的含义是参加前i件项目的总时间和

3.二分答案进行枚举,在确定了最后一个项目的前提下,看前面最多能参加多少项目,使得刚好不超过总时间,参加i个项目的总时间就是s[i](这是因为前面已经进行了排序、前缀和)

需要注意的是,如果前x件项目中包含枚举的i,我们需要将其删去并将s[x]改为s[x+1]。

总结:这种方法相当于改变了贪心策略,把原先暴力贪心的策略:将最后一个项目(不计算休息时间)和前面所有项目(计算休息时间)放在一起贪心,改变成了以下策略:先枚举最后一个项目,再对前面所有项目进行贪心,这时采用暴力贪心即可,因为前面所有项目都必须计算休息时间,所以直接把两个时间加起来排序就行,这已经是最优的策略了,局部最优解一定能得到整体最优解。

以下是完整代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 4e5;
int n,k,ans;
long long s[N];
struct node {
  int et,br;
}item[N];
//按总时间从小到大排序
bool cmp(const node &a, const node &b) {
  return a.et + a.br <= b.et + b.br;
}
//如果前x件项目中包含枚举的y,我们需要将其删去并将s[x]改为s[x+1]。
bool check(int x, int y,int time) {
  //前x件项目中不包含枚举的y
  if(x < y) {
    if(s[x] <= time) return true;//参与前x项,可以在剩余时间内完成
    else return false;           //参与前x项,不能在剩余时间内完成
  }
  //前x件项目中包含枚举的y
  else {
    if(x + 1 > n) return false;
    if(s[x + 1] - item[y].et - item[y].br <= time) return true;
    else return false;
  }
}
int main() {
  //输入
  ios::sync_with_stdio(false),cin.tie(0);
  cin>>n>>k;
  for(int i = 1; i <= n; i ++) cin>>item[i].et;
  for(int i = 1; i <= n; i ++) cin>>item[i].br;
  //排序
  sort(item+1,item+1+n,cmp);
  //前缀和:前i件项目的时间和
  for(int i = 1; i <= n; i ++) s[i] = s[i-1] + item[i].et + item[i].br;
  //枚举每一个项目,作为最后一个项目
  for(int i = 1; i <= n; i ++) {
    int temp = k;
    if(temp < item[i].et) continue;//如果这个项目的比赛时间直接大于总时间了就直接排除
    temp -= item[i].et;//算出剩余时间
    //二分答案进行枚举
    int l = 0, r = n;//从1~n个项目
    while(l + 1 != r) {
      int mid = (l + r) >> 1;
      if(check(mid,i,temp)) l = mid;//参与前mid项,可以在剩余时间内完成
      else r = mid;                 //参与前mid项,不能在剩余时间内完成
    }
    ans = max(l+1,ans); //l项加上之前枚举的最后一项,共l+1项
    }
  cout<<ans;
  return 0;
}

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于 jQuery 的事件,我可以和你分享一些学习笔记。 1. 绑定事件 在 jQuery 中,我们可以通过以下方式来绑定事件: ``` $(selector).event(function(){ // 事件处理程序 }) ``` 其中,`selector` 表示要绑定事件的元素,`event` 表示要绑定的事件类型,比如 `click`、`mouseover` 等等。事件处理程序则是在事件触发时要执行的代码块。 2. 多个事件绑定 我们可以通过 `on()` 方法来同时绑定多个事件: ``` $(selector).on({ event1: function(){ // 事件处理程序1 }, event2: function(){ // 事件处理程序2 } }) ``` 这样,当 `event1` 或 `event2` 中任意一个事件触发时,对应的处理程序都会被执行。 3. 解除事件 如果需要解除某个元素的事件处理程序,可以使用 `off()` 方法: ``` $(selector).off(event); ``` 其中,`event` 表示要解除的事件类型。如果不指定事件类型,则会解除该元素上所有的事件处理程序。 4. 事件委托 在 jQuery 中,我们可以使用事件委托来提高性能。事件委托是指将事件绑定到父元素上,而不是绑定到子元素上,然后通过事件冒泡来判断是哪个子元素触发了该事件。这样,当子元素数量较多时,只需要绑定一次事件,就可以监听到所有子元素的事件。 ``` $(selector).on(event, childSelector, function(){ // 事件处理程序 }) ``` 其中,`selector` 表示父元素,`event` 表示要绑定的事件类型,`childSelector` 表示要委托的子元素的选择器,事件处理程序则是在子元素触发事件时要执行的代码块。 以上是 jQuery 中事件的一些基本操作,希望对你有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值