【237题】算法基础精选题单 刷题笔记DAY3 二分答案 中

NC235254 晾衣服

https://ac.nowcoder.com/acm/problem/235254
来源:牛客网

首先看到这题的时候想到的是贪心而并非是二分答案,一开始的贪心的思路是从大到小湿度的衣服运用烘干依次晒干(当然,能自然风干的不需要再烘干,只需要去记录一个自然风干的值就行)

但后来发现自己的贪心思路有误,最大湿度的衣服不一定烘干到底,就比如一分钟烘干10度,而衣服湿度为12,那么剩下的2湿度可以留给自然烘干—>那么正确思路应该是对数组中每次最大湿度的衣服使用烘干机,并不断更新数组中的元素---->即本质为降低平均湿度,使得自然风干的衣服最大化

但题目数据范围实在太大,一次次减去包被超时,所以兜兜转转还是回到二分答案qwq

那二分答案的右边界是什么呢?注意到k最小为1,最坏情况下我们可以选取数组最大值以1度慢慢烘干,其他自然风干的时间作为右边界

下面是代码
感觉有一个坑点就是烘干是包含风干的,在check中减去风干的之后,需要除以k-1而非k

#include<bits/stdc++.h>
#include<cmath>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int len, n, m;
int s[N];
int d[N];
int sum[N];
int tempd[N];

int check(int mid)
{
  //cout<<mid<<endl<<endl;
  int cnt=0;
  for(int i=0;i<n;i++)
  {
    double tempm=m;
     if(s[i]<=mid) continue;
     double now=s[i]-mid;
     cnt+=ceil(now/(tempm-1));
     //cout<<cnt<<endl;
     if(cnt>mid) return false;  
  }
 return true;
}

signed main()
{
  int r = 0;
  cin >> n;
  for (int i = 0; i < n; i++)
  cin >> s[i],r=max(r,s[i]);
  cin >> m;
  if(m==1) cout<<r<<endl,exit(0);
  int l = 1;
  r=r+10;//防止边界问题
  while (l < r)
  {
    int mid = (l + r) >> 1;
    if (check(mid))
      r = mid;
    else
      l = mid + 1;
  }
  cout << l << endl;
}

[USACO 2010 Feb S]Chocolate Eating

链接:https://ac.nowcoder.com/acm/problem/24724
来源:牛客网

坐半天发现好多地方是错的,心碎了qwq,思路的不同导致要处理多好多细节,以后再做之前一定要考虑好,对于我将要处理的对象,我要考虑到的细节,以及我是否能通过改变对象去简化操作。
在check中其实我不应该去枚举每个巧克力,记录每个在哪天吃,因为尽管使得每块巧克力的安排能满足二分的mid,但我要去判断在安排完所有巧克力后,昨天剩余的能否去满足剩下的天数,那么我就要多次进行除二判断了

个人理解:对于天数,他有一个明确的范围,变化清晰,且在天数结束后,巧克力必定被吃完,所以枚举天数为优,而对于巧克力,其对应的快乐指数不定,细节处理比较繁杂。(也可能是我蔡)qwq

最重要的一点是我没考虑到,对于每次二分,我的程序都会不管三七二十一去记录巧克力的使用日期,导致我最后的ans数组不一定是最优解,所以最好去用一个全局变量去存储,然后得出最优解后再将答案跑一遍!!!*

其次我的循环操作也处理得不是很好,很多步骤的先后次序有问题,导致细节出问题

先把正常的代码贴出来吧,另一篇思一样的代码就不要看了

正常代码

#include <iostream>
#include <cmath>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int len, n, m;
int s[N];
int ans[N];
int res = 0;
int check(int mid)
{
  int cnt=0;
  int now=0;//巧克力序号
  for(int i=1;i<=m;i++)//枚举每一天
  {
      cnt/=2;//先除二,昨天剩余的是否能满足今天
      if(cnt>=mid) continue;
       while(now<n&&cnt<mid)
       {
        cnt+=s[now];
        ans[now++]=i;
        //if(now>=n&&cnt<mid) return false;
       }
       if(now>=n&&cnt<mid) return false;


  }
  //别忘了剩下的巧克力也要吃完//又wa了一发qwq
  for(;now<n;now++)
  {
    ans[now]=m;
  }
  return true;
  //终于过了,太不容易了qwq qwq
}

signed main()
{
  int r = 0;
  cin >> n >> m;
  for (int i = 0; i < n; i++)
    cin >> s[i], r += s[i];
  int l = 0;
  while (l < r)
  {
    int mid = (l + r + 1) >> 1;
    if (check(mid))
      res = mid, l = mid;
    else
      r = mid - 1;
  }
  cout << res << endl;
  check(res);
  for (int i = 0; i < n; i++)
    cout << ans[i] << endl;
}

不正常的代码

//我是文学家,这就是相思
//思维很混乱,万泉部诗人
#include <iostream>
#include <cmath>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int len, n, m;
int s[N];
int ans[N];
int res = 0;
int check(int mid)
{
  // int tempans[51000];
  int cnt = 0;
  int now = 0;
  for (int i = 0; i < n;)
  {
    cnt /= 2;
    now++; ///
    int j = i;
    if (now > m)
    {
      for (; j < n; j++)
        ans[j] = m;
      break;
    }

    while (j < n && cnt < mid)
    {
      // cout<<1<<endl;
      cnt += s[j];
      ans[j] = now;
      // cout<<ans[j]<<endl;
      j++;
    }
    if (j >= n && cnt < mid)
      return false;
    i = j;
    
  }
  if (now < m)
    {
      for(int i=0;i<m-now;i++)
      {
        cnt/=2;
        if(cnt<mid) return false;
      }
    }
  return true;
}

signed main()
{
  int r = 0;
  cin >> n >> m;
  for (int i = 0; i < n; i++)
    cin >> s[i], r += s[i];
  int l = 0;
  while (l < r)
  {
    int mid = (l + r + 1) >> 1;
    if (check(mid))
      res = mid, l = mid;
    else
      r = mid - 1;
  }
  cout << res << endl;
  check(res);
  for (int i = 0; i < n; i++)
    cout << ans[i] << endl;
}

NC235260 数字组合

链接:https://ac.nowcoder.com/acm/problem/235260
来源:牛客网

注意到数据范围为1000,首先考虑暴力加优化,枚举四层循环复杂度为O(n^4),即使将最后一层改为二分也显然超时,那么该怎么做呢

题目只要求去输出方案数,而对具体数字不做要求,那么我们是否可以去预处理一下数据(反正不需要我们输出哪些数据的和)来降低复杂度呢?

于是我们考虑先预处理出前两行和后两行数字的所有和的数量,然后二分查找相反数,则复杂度变为O(n2 + n2logn),显然满足题意

注意到需要记录前后数量,那么其实我们可以用hash存储,那么查找的复杂度降低为O(1),总体复杂度降低为O(n),注意要记录每个和的数量!!!!

#include <bits/stdc++.h>
using namespace std;
#define int long long

const int N = 2e6 + 10;//段错误
int n;
int a[N], b[N], c[N], d[N];
int l[N];
int r[N];
map<int,int>M;

signed main()
{
  cin >> n;
  for (int i = 0; i < n; i++)
  {
    cin >> a[i] >> b[i] >> c[i] >> d[i];
  }
  int cnt = 0;
  for (int i = 0; i < n; i++)
  {
    for (int j = 0; j < n; j++)
    {
      l[cnt] = a[i] + b[j];
      r[cnt] = c[i] + d[j];
      M[r[cnt]]++;cnt++;
    }
  }
  sort(l, l + cnt);
  sort(r, r + cnt);
  int ans=0;
     for(int i=0;i<cnt;i++)
     {
       int now=-l[i];
       if(M.count(now))
       ans+=M[now];
     }
     
  cout<<ans<<endl;
}

NC19916 [CQOI2010]扑克牌

题目要求:1.每套牌必须包含所有不同数字,joker可看作万能牌替代任意一张。2求最多可以组成的套数

思考方向:首先考虑的是枚举答案,从大到小去考虑做多能用joker凑出多少套牌,但数据范围为5e8,会被超时,所以优化为二分枚举。

check函数根据每个数字的缺少牌数,然后得出joker的消耗数,满足两个条件–>1.小于joker数 2.小于套数(我没考虑到的,因为每套只能至多存在一张)

在check的时候可以排个序,每个数量的牌的点数并不影响判断。当我判断到牌数大于套数的时候可以直接break

#include <iostream>
#include <algorithm>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int n, m;
int s[N];

int check(int mid)
{
  int tempm = m;
  // cout<<mid<<endl;
  int cnt = 0;
  for (int i = 0; i < n; i++)
  {
     int now = s[i];
     if(s[i]>=mid) break;
     int d=mid-s[i];
     tempm-=d;
     if(tempm<0) return false;//有可能用了多张j牌
  }
    if(m-tempm<=min(mid,m))//少了这一步判断,也是没考虑到的
    return true;
    else return false;
}

signed main()
{
  cin >> n >> m;
  int cnt = 0;
  for (int i = 0; i < n; i++)
    cin >> s[i], cnt += s[i];
  sort(s, s + n);
  int l = 0;
  int r = cnt;
  while (l < r)
  {
    int mid = (l + r + 1) >> 1;
    if (check(mid))
      l = mid;
    else
      r = mid - 1;
    // cout<<endl;
  }
  cout << l << endl;
}

也有不排序直接统计的版本,思路更加清晰,有助于理解

#include <iostream>
#include <algorithm>
using namespace std;
#define int long long

const int N = 2e5 + 10;
int n, m;
int s[N];

int check(int mid)
{
  int tempm = m;
  // cout<<mid<<endl;
  int cnt = 0;
  for (int i = 0; i < n; i++)
  {
    cnt += max(0ll, mid - s[i]);
  }
  if (cnt <= min(mid, m))
    return 1; // 由于j牌每组只能用一张,所以其应该小于总组数
  else
    return 0;
}

signed main()
{
  cin >> n >> m;
  int cnt = 0;
  for (int i = 0; i < n; i++)
    cin >> s[i], cnt += s[i];
  sort(s, s + n);
  int l = 0;
  int r = cnt;
  while (l < r)
  {
    int mid = (l + r + 1) >> 1;
    if (check(mid))
      l = mid;
    else
      r = mid - 1;
    // cout<<endl;
  }
  cout << l << endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值