洛谷 P1873 砍树 (二分 简单)

【二分答案】是分治的一种,这类问题很经典,接下来几篇文章会关于二分答案相关的文章,希望同学们可以完成10道以上的【二分答案】相关问题,以此来加深对【二分答案】这类问题的个人理解。
原公众号链接:分治第二讲:二分答案之砍树问题

一、题目

题目链接:

https://www.luogu.com.cn/problem/P1873

题意:找到一个最恰当的高度砍树,使得砍树得到的树木高度之和刚好大于等于M即可。看题目数据范围,1<=n<=10^6,通过这个数据范围,可以反推出你的算法时间复杂度只能是低于O(nlogn),因此瞬间想到二分,往二分上靠拢。

二、题目分析

题意很简单,如何求解呢?其实会发现,直接求解不是很容易求解,该问题可以归纳总结为【最小值最大问题】,即需要求解的砍树高度,在满足题目要求的情况下要最大。【最小值最大】和【最大值最小】问题均可以通过【二分答案】思路完成,在完成【二分】之前,我们先用暴力实现,然后再修改为二分即可。

对于任何一个问题,一般有两种方式解决,一种是直接求解,另一种是检验,检验指的是给一个可能解,带入题目中检查该解是否满足题目,检验类似于数学中的选择题,如果不会直接求解,那么可以依次把四个选项带入题目中检验验证即可;我个人认为:检验的难度小于直接求解。

该题目求解最恰当的砍树高度h,假设最高的那棵树的高度为maxH, 则h的取值范围为[0, maxH];可以依次让k从maxH开始依次递减直到0,递减过程中,如果某个取值恰当满足砍的树的高度之和大于等于M,则当前k就是最终解,直接输出并结束程序即可。

三、题目解决

上一部分分析了题目的求解方法并可以通过暴力的方法依次从maxH到0遍历,找到第一个满足题目要求的高度即可,试想一个问题,如果某个高度hi砍树不能满足砍掉的树的高度和大于等于M,那么对于大于等于hi的高度hj来说也无法满足题意,理解了上面这个特性就可以通过二分来快速求解高度h了。

二分的左边界left为0,右边界right为maxH,在这里扩展一个点,如果根据题意不好确定二分的初始边界left和right,那么可以直接暴力将left初始化为0,right初始化为一个很大的值,比如1e8;

Step1:取二分的中间值mid = left + (right-left)/2,检查以mid为砍树高度,是否可以得到高度大于等于M的树木;

Step2:如果不行,那么大于等于mid的高度均无法得到,因此缩小二分区间,right = mid-1;

Step3:如果可以,那么mid就是可能的一个解,保存在ans中,缩小二分区间,left = mid+1; (一定要记住,使用二分,禁止出现left = mid,因为这样很有可能陷入死循环,所以务必要避免left = mid,但是可以有right = mid);

二分问题解决了,如何实现检验函数check呢?也很简单,给你一个高度mid,遍历所有树木的高度,用sum记录砍掉的树的高度和,则如果当前树的高度小于mid,则没有得到树木,否则,在当前树上可以砍下树的高度减掉mid的差值的高度的树木,最终只需要判断sum是否大于M即可。

四、code编码

暴力代码O(n^2)


#include "iostream"
using namespace std;
const int M = 1000010;
int n, m, a[M], l = 0, r = -1, ans = -1;

// check函数的功能是:给一个高度x,返回该高度是否可以砍出高度和为m的树木,如果可以返回true,否则返回false
bool check(int x) {
  long long sum = 0;
  for(int i=1; i<=n; i++) {  // 遍历所有树木
    sum += max(0, a[i]-x);  // 求解x的高度,每个树可以砍多少树木
  }
  return sum >= m;   // 砍出的总和是否大于m,如果大于m则返回true,否则返回false;
} 

int main() {
  cin >> n >> m;
  for(int i=1; i<=n; i++) {
    cin >> a[i];
    r = max(r, a[i]);  // 确定右边界,为最高的树的高度
  }
  for(int x = r; x>=l; x--) {
    if(check(x)) {  
      cout << x << endl;
      return 0;
    }
  }
  
}

暴力超时6个点,AC了4个点,因此通过二分优化如下:

二分代码O(nlogn)


#include "iostream"
using namespace std;
const int M = 1000010;
int n, m, a[M], l = 0, r = -1, ans = -1;

// check函数的功能是:给一个高度x,返回该高度是否可以砍出高度和为m的树木,如果可以返回true,否则返回false
bool check(int x) {
  long long sum = 0;
  for(int i=1; i<=n; i++) {  // 遍历所有树木
    sum += max(0, a[i]-x);  // 求解x的高度,每个树可以砍多少树木
  }
  return sum >= m;   // 砍出的总和是否大于m,如果大于m则返回true,否则返回false;
} 

int main() {
  cin >> n >> m;
  for(int i=1; i<=n; i++) {
    cin >> a[i];
    r = max(r, a[i]);  // 确定右边界,为最高的树的高度
  }
  while(l <= r) {  // 二分答案
    int mid = l + (r-l)/2;  // 找出区间的中间值
    if(check(mid)) {  // 如果当前mid高度可以砍出m的树木高度,则说明mid是一个可能解,保存并缩小区间
      ans = mid;  // 保存可能解
      l = mid + 1; // 缩小区间
    } else {
      r = mid-1;  // mid高度不行,因此需要缩小右边界,答案在左区间
    }
  }
  cout << ans;
  
}

二分细节是魔鬼呀,琢磨什么时候是while(left<right),什么时候是while(left <= right)?

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 题目描述: Eko 有一排树,每棵树的高度不同。他想要砍掉一些树,使得剩下的树的高度都相同。他希望砍掉的树的高度尽可能地少,你能帮他算出最少要砍掉多少棵树吗? 输入格式: 第一行包含两个整数 N 和 M,分别表示树的数量和 Eko 希望的树的高度。 第二行包含 N 个整数,表示每棵树的高度。 输出格式: 输出一个整数,表示最少要砍掉的树的数量。 输入样例: 9 5 2 3 4 7 8 9 10 11 12 输出样例: 3 解题思路: 二分答案 首先,我们可以发现,如果我们知道了 Eko 希望的树的高度,那么我们就可以计算出砍掉多少棵树。 具体来说,我们可以遍历每棵树,如果它的高度大于 Eko 希望的树的高度,那么就将它砍掉,否则就保留它。 然后,我们可以使用二分答案的方法来确定 Eko 希望的树的高度。 具体来说,我们可以将树的高度排序,然后二分一个可能的 Eko 希望的树的高度,然后计算砍掉多少棵树,如果砍掉的树的数量小于等于 M,那么说明 Eko 希望的树的高度可能更小,否则说明 Eko 希望的树的高度可能更大。 最后,我们可以得到最少要砍掉的树的数量。 时间复杂度:O(NlogN)。 参考代码: ### 回答2: 这道题目是一道模拟题,需要模拟机器人的移动过程以及得出最终机器人的位置和朝向。首先需要明确机器人的起始位置以及朝向,其次需要读取输入的指令,根据指令逐步移动机器人,并顺便判断是否会越界或者碰到障碍物。最后输出最终机器人的位置和朝向。 在本题中,需要按照从西向东、从北向南、从东向西、从南向北的顺序判断机器人的朝向。为了方便表述,我把机器人的朝向表示为0、1、2、3,分别代表从西向东、从北向南、从东向西、从南向北。 具体地说,机器人按照指令逐步移动时需要分情况讨论,比如: 1.当前机器人朝向为0,即从西向东: 若指令为F,则x坐标+1,但需要判断是否越界或者碰到障碍物。 若指令为L,则朝向变为3。 若指令为R,则朝向变为1。 2.当前机器人朝向为1,即从北向南: 若指令为F,则y坐标-1,但需要判断是否越界或者碰到障碍物。 若指令为L,则朝向变为0。 若指令为R,则朝向变为2。 3.当前机器人朝向为2,即从东向西: 若指令为F,则x坐标-1,但需要判断是否越界或者碰到障碍物。 若指令为L,则朝向变为1。 若指令为R,则朝向变为3。 4.当前机器人朝向为3,即从南向北: 若指令为F,则y坐标+1,但需要判断是否越界或者碰到障碍物。 若指令为L,则朝向变为2。 若指令为R,则朝向变为0。 最后输出最终机器人的位置和朝向即可。 在编写程序时需要注意判断边界和障碍物,以及要用scanf读取输入,不要用C++的cin,否则会TLE。此外,由于本题没有给出边界和障碍物,需要自己设置。最后,本题的思路不难,但是需要认真仔细地处理各种情况,多测试几组数据找出程序的漏洞,这样才能通过本题。 ### 回答3: 本题为一道组合数学题,需要运用排列组合知识进行分析。 题目要求将n个方块填入3*3的网格中,每个方块可以是红色、绿色或蓝色的一个。要求每行、每列和对角线上的方块颜色都不相同。求方案总数。 首先考虑对第一行进行颜色选取。由于第一行每个位置的颜色都不影响其他行和列,故第一行的颜色选取不影响总方案数。所以假设第一行颜色已经确定,考虑第二行的颜色选取。第二行中各位置的颜色受到第一行的限制,只有第一行某位置颜色的补集才能选取。例如,若第一行第一个位置是红色,那么第二行第一个位置不能选取红色。因为每行颜色不能相同,所以第二行受到第一行限制的位置只有3个。第三行同理,由于前两行的限制,只有2个位置可选。做完颜色选取后,再将每行的方块进行排列,此时我们可以使用错排公式得到方案数: D(n) = n!(1 - 1/1! + 1/2! - 1/3! + ... + (-1)^(n)/n!) 最终,方案总数即为每个第一行颜色选取方法下的错排方案数之和。按题意枚举第一行的颜色,就可以得到最终的方案总数了。 总结一下,本题所需要的知识点为:错排公式、颜色限制对组合数的影响、暴力枚举法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值