算法小白的努力之路———洛谷刷题打卡 ———— 二分查找与二分答案

算法小白的努力之路———洛谷刷题打卡 ———— 二分查找与二分答案

前言 这里用的模板是参考了五点七边所传授的二分法,可以参考这个视频

二分查找为什么总是写错?

这里写一个二分模板,并附上边界条件的判断方法

整数二分模板

int binarySearch()
{
	int l = -1, r = n;
	while(l + 1 < r)
	{
		int mid = (l + r) >> 1; //右移一位相当于除以二
		if(confition)
		{
			l = mid; 
		}
		else
		{
			r  = mid;
		}
	}
	return l; // 看情况返回 l 或 r ,有时可以返回其他的,还是看题目
}

这里是边界条件的判断,IsBlue 就是模板中的 confition
在这里插入图片描述
小数二分模板,唯一的不同在 whileconfition判断上

int binarySearch()
{
	int l = -1, r = n;
	while(r - l > 0.001) //不一定是0.001, >号后面更题目所需的精度
	{
		int mid = (l + r) >> 1; //右移一位相当于除以二
		if(confition)
		{
			l = mid; 
		}
		else
		{
			r  = mid;
		}
	}
	return l; // 不存在取左边还是右边的问题,随意返回一边就可以
}

另外小数不存在取左边还是右边的问题,随意返回一边就可以

正式开始打卡 ↓↓↓

1. P2249 【深基13.例1】查找

题目描述

输入 n n n 个不超过 1 0 9 10^9 109 的单调不减的(就是后面的数字不小于前面的数字)非负整数 a 1 , a 2 , … , a n a_1,a_2,\dots,a_{n} a1,a2,,an,然后进行 m m m 次询问。对于每次询问,给出一个整数 q q q,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 − 1 -1 1

输入格式

第一行 2 2 2 个整数 n n n m m m,表示数字个数和询问次数。

第二行 n n n 个整数,表示这些待查询的数字。

第三行 m m m 个整数,表示询问这些数字的编号,从 1 1 1 开始编号。

输出格式

输出一行, m m m 个整数,以空格隔开,表示答案。

样例 #1

样例输入 #1

11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6

样例输出 #1

1 2 -1

提示

数据保证, 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1n106 0 ≤ a i , q ≤ 1 0 9 0 \leq a_i,q \leq 10^9 0ai,q109 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1m105

本题输入输出量较大,请使用较快的 IO 方式。

--------------------------------------------------------------------

题解 ↑↑↑

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1000010;
int n, m;
int q[N];

int binarySearch(int x)
{
    int l = 0, r = n + 1;
    while (l + 1 < r)
    {
        int mid = (l + r) >> 1;
        if (q[mid] < x)
        {
            l = mid;
        }
        else
        {
            r = mid;
        }
    }
    
    if (q[r] == x) return r;  // 找到了目标元素
    else return -1;  // 没有找到目标元素
}

int main()
{
    scanf("%d%d", &n, &m);  // 输入数字个数和查询次数
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &q[i]);  // 输入数字序列
    }
    while (m--)
    {
        int x;
        scanf("%d", &x);  // 输入查询数字
        printf("%d ", binarySearch(x));  // 输出查询结果
    }
    return 0;
}

--------------------------------------------------------------------

2. P1102 A-B 数对

题目背景

出题是一件痛苦的事情!

相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!

题目描述

给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 N , C N,C N,C

第二行, N N N 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 A − B = C A - B = C AB=C 的数对的个数。

样例 #1

样例输入 #1

4 1
1 1 2 3

样例输出 #1

3

提示

对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1N2000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1N2×105 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0ai<230 1 ≤ C < 2 30 1 \leq C < 2^{30} 1C<230

2017/4/29 新添数据两组

--------------------------------------------------------------------

题解 ↑↑↑

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;  // 定义数据类型为 long long,用于存储结果

const int N = 200010;
int n, c;
int q[N];
LL res;

void binarySearch()
{
    for (int i = 0; i < n; i++)
    {
        int l = -1, r = n;
        while (l + 1 < r)
        {
            int mid = (l + r) >> 1;  // 二分查找的中间位置
            if (q[mid] < q[i] + c)  // 如果 q[mid] 小于 q[i] + c,则说明满足条件的数对在右侧
            {
                l = mid;
            }
            else
            {
                r = mid;
            }
        }
        LL res1 = r;  // 记录右侧边界的位置
        
        l = -1, r = n;
        while (l + 1 < r)
        {
            int mid = l + r >> 1;  // 二分查找的中间位置
            if (q[mid] <= q[i] + c)  // 如果 q[mid] 小于等于 q[i] + c,则说明满足条件的数对在右侧
            {
                l = mid;
            }
            else
            {
                r = mid;
            }
        }
        LL res2 = l;  // 记录左侧边界的位置
        res += (res2 - res1 + 1);  // 将满足条件的数对个数累加到结果中
    }
}

int main()
{
    cin >> n >> c;  // 输入数组长度和 c 的值
    for (int i = 0; i < n; i++)
    {
        cin >> q[i];  // 输入数组元素
    }
    sort(q, q + n);  // 对数组进行排序
    binarySearch();  // 进行二分查找
    cout << res << endl;  // 输出结果
    return 0;
}

温馨提示:必须用 long long 储存答案 ,因为有一个数据答案是10000000000,不用只能得92分。

--------------------------------------------------------------------

3. P1873 [COCI2011-2012#5] EKO / 砍树

题目描述

伐木工人 Mirko 需要砍 M M M 米长的木材。对 Mirko 来说这是很简单的工作,因为他有一个漂亮的新伐木机,可以如野火一般砍伐森林。不过,Mirko 只被允许砍伐一排树。

Mirko 的伐木机工作流程如下:Mirko 设置一个高度参数 H H H(米),伐木机升起一个巨大的锯片到高度 H H H,并锯掉所有树比 H H H 高的部分(当然,树木不高于 H H H 米的部分保持不变)。Mirko 就得到树木被锯下的部分。例如,如果一排树的高度分别为 20 , 15 , 10 20,15,10 20,15,10 17 17 17,Mirko 把锯片升到 15 15 15 米的高度,切割后树木剩下的高度将是 15 , 15 , 10 15,15,10 15,15,10 15 15 15,而 Mirko 将从第 1 1 1 棵树得到 5 5 5 米,从第 4 4 4 棵树得到 2 2 2 米,共得到 7 7 7 米木材。

Mirko 非常关注生态保护,所以他不会砍掉过多的木材。这也是他尽可能高地设定伐木机锯片的原因。请帮助 Mirko 找到伐木机锯片的最大的整数高度 H H H,使得他能得到的木材至少为 M M M 米。换句话说,如果再升高 1 1 1 米,他将得不到 M M M 米木材。

输入格式

1 1 1 2 2 2 个整数 N N N M M M N N N 表示树木的数量, M M M 表示需要的木材总长度。

2 2 2 N N N 个整数表示每棵树的高度。

输出格式

1 1 1 个整数,表示锯片的最高高度。

样例 #1

样例输入 #1

4 7
20 15 10 17

样例输出 #1

15

样例 #2

样例输入 #2

5 20
4 42 40 26 46

样例输出 #2

36

提示

对于 100 % 100\% 100% 的测试数据, 1 ≤ N ≤ 1 0 6 1\le N\le10^6 1N106 1 ≤ M ≤ 2 × 1 0 9 1\le M\le2\times10^9 1M2×109,树的高度 < 1 0 9 <10^9 <109,所有树的高度总和 > M >M >M

--------------------------------------------------------------------

题解 ↑↑↑

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 1000010;
int n, m;
int q[N];

// 找到数组中的最大值的索引
int tmax(int q[])
{
    int tmax = 0;
    for(int i = 0; i < n; i ++)
    {
        if(q[i] > q[tmax]) tmax = i;
    }
    return q[tmax];
}

// 二分查找
LL binarySearch()
{
    int l = 0, r = tmax(q);
    while(l + 1 < r)
    {
        int mid = (l + r) >> 1;
        LL res1 = 0;
        
        // 遍历每棵树,计算总木材长度
        for(int i = 0; i < n; i ++)
            if(q[i] - mid >= 0) res1 += q[i] - mid;
        
        if(res1 < m)
        {
            r = mid;
        }
        else 
        {
            l = mid;
        }
    }
    return l;
}

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i ++)
    {
        cin >> q[i];
    }
    sort(q, q + n);
    LL res = binarySearch();
    cout << res << endl;
    return 0;
}

这段代码使用了二分查找来寻找最大高度。首先,通过tmax函数找到树木中的最大高度,将其作为初始的右边界。然后,进入二分查找循环,直到左边界大于右边界。在每次循环中,计算当前中间高度对应的总木材长度,如果总木材长度小于需要的木材总长度,则将右边界更新为当前中间高度;否则,将左边界更新为当前中间高度。最后,输出左边界作为锯片的最大高度。

请注意,这段代码假设输入的树木高度已经按照升序排序。如果输入时未排序的,需要先对树木高度进行排序。

--------------------------------------------------------------------

4. P1024 [NOIP2001 提高组] 一元三次方程求解

题目描述

有形如: a x 3 + b x 2 + c x + d = 0 a x^3 + b x^2 + c x + d = 0 ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数( a , b , c , d a,b,c,d a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 − 100 -100 100 100 100 100 之间),且根与根之差的绝对值 ≥ 1 \ge 1 1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 2 2 位。

提示:记方程 f ( x ) = 0 f(x) = 0 f(x)=0,若存在 2 2 2 个数 x 1 x_1 x1 x 2 x_2 x2,且 x 1 < x 2 x_1 < x_2 x1<x2 f ( x 1 ) × f ( x 2 ) < 0 f(x_1) \times f(x_2) < 0 f(x1)×f(x2)<0,则在 ( x 1 , x 2 ) (x_1, x_2) (x1,x2) 之间一定有一个根。

输入格式

一行, 4 4 4 个实数 a , b , c , d a, b, c, d a,b,c,d

输出格式

一行, 3 3 3 个实根,从小到大输出,并精确到小数点后 2 2 2 位。

样例 #1

样例输入 #1

1 -5 -4 20

样例输出 #1

-2.00 2.00 5.00

提示

【题目来源】

NOIP 2001 提高组第一题

--------------------------------------------------------------------

题解 ↑↑↑

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

double a, b, c, d; // 输入的系数
double x1, x2; // 函数值
int num; // 计数器

double f(double x)
{
    return a * x * x * x + b * x * x + c * x + d; // 定义函数 f(x)
}

void binarySearch()
{
    for (double i = -100; i <= 100; i++)
    {
        double l = i, r = i + 1; // 区间左右边界
        x1 = f(l); // 计算左边界的函数值
        x2 = f(r); // 计算右边界的函数值
        if (!x1)
        {
            printf("%.2lf ", l); // 如果左边界的函数值为零,则输出左边界作为根
            num++;
        }
        if (x1 * x2 < 0)
        {
            while (r - l >= 0.001) // 当区间长度大于等于 0.001 时进行迭代
            {
                double mid = (l + r) / 2; // 取区间的中点
                if (f(mid) * f(r) < 0) // 如果中点和右边界的函数值异号
                {
                    l = mid; // 更新左边界为中点
                }
                else
                {
                    r = mid; // 更新右边界为中点
                }
            }
            printf("%.2lf ", r); // 输出找到的根
            num++;
        }
        if (num == 3) // 如果找到了三个根,则退出循环
        {
            break;
        }
    }
}

int main()
{
    cin >> a >> b >> c >> d; // 输入系数
    
    binarySearch(); // 进行二分查找
    
    return 0;
}

这段代码通过二分查找的方式寻找给定函数的根。它遍历了区间 [-100, 100],计算左右边界的函数值,并根据函数值的符号进行判断和迭代,直到找到满足条件的根或者找到了三个根为止。最后,输出找到的根。

--------------------------------------------------------------------

5. P1678 烦恼的高考志愿

题目背景

计算机竞赛小组的神牛 V 神终于结束了高考,然而作为班长的他还不能闲下来,班主任老 t 给了他一个艰巨的任务:帮同学找出最合理的大学填报方案。可是 v 神太忙了,身后还有一群小姑娘等着和他约会,于是他想到了同为计算机竞赛小组的你,请你帮他完成这个艰巨的任务。

题目描述

现有 m m m 所学校,每所学校预计分数线是 a i a_i ai。有 n n n 位学生,估分分别为 b i b_i bi

根据 n n n 位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。

输入格式

第一行读入两个整数 m , n m,n m,n m m m 表示学校数, n n n 表示学生数。

第二行共有 m m m 个数,表示 m m m 个学校的预计录取分数。第三行有 n n n 个数,表示 n n n 个学生的估分成绩。

输出格式

输出一行,为最小的不满度之和。

样例 #1

样例输入 #1

4 3
513 598 567 689
500 600 550

样例输出 #1

32

提示

数据范围:

对于 30 % 30\% 30% 的数据, 1 ≤ n , m ≤ 1000 1\leq n,m\leq1000 1n,m1000,估分和录取线 ≤ 10000 \leq10000 10000

对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 100000 1\leq n,m\leq100000 1n,m100000,估分和录取线 ≤ 1000000 \leq 1000000 1000000 且均为非负整数。

--------------------------------------------------------------------

题解 ↑↑↑

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 100010;
int m, n;
int a[N], b[N];
LL res = 0;

LL binarySearch()
{
    for (int i = 0; i < n; i++)
    {
        int l = -1, r = m;
        while (l + 1 < r)
        {
            int mid = (l + r) >> 1; // 二分查找的中间位置
            if (a[mid] <= b[i])
            {
                l = mid; // 如果中间位置的值小于等于目标值,则更新左边界
            }
            else
            {
                r = mid; // 否则,更新右边界
            }
        }

        if (b[i] <= a[0])
        {
            res += a[0] - b[i]; // 如果目标值小于等于数组a的最小值,则累加差值到结果中
        }
        else
        {
            res += min(abs(a[l] - b[i]), abs(a[r] - b[i])); // 否则,计算与两个边界值的差的绝对值,并取最小值累加到结果中
        }
    }
    return res;
}

int main()
{
    cin >> m >> n;

    // 输入数组a的元素
    for (int i = 0; i < m; i++)
        cin >> a[i];

    sort(a, a + m); // 对数组a进行排序

    // 输入数组b的元素
    for (int i = 0; i < n; i++)
        cin >> b[i];

    cout << binarySearch() << endl; // 输出结果

    return 0;
}

--------------------------------------------------------------------

6. P2440 木材加工

题目背景

要保护环境

题目描述

木材厂有 n n n 根原木,现在想把这些木头切割成 k k k 段长度 l l l 的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 l l l 的最大值。

木头长度的单位是 cm \text{cm} cm,原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

例如有两根原木长度分别为 11 11 11 21 21 21,要求切割成等长的 6 6 6 段,很明显能切割出来的小段木头长度最长为 5 5 5

输入格式

第一行是两个正整数 n , k n,k n,k,分别表示原木的数量,需要得到的小段的数量。

接下来 n n n 行,每行一个正整数 L i L_i Li,表示一根原木的长度。

输出格式

仅一行,即 l l l 的最大值。

如果连 1cm \text{1cm} 1cm 长的小段都切不出来,输出 0

样例 #1

样例输入 #1

3 7
232
124
456

样例输出 #1

114

提示

数据规模与约定

对于 100 % 100\% 100% 的数据,有 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105 1 ≤ k ≤ 1 0 8 1\le k\le 10^8 1k108 1 ≤ L i ≤ 1 0 8 ( i ∈ [ 1 , n ] ) 1\le L_i\le 10^8(i\in[1,n]) 1Li108(i[1,n])

--------------------------------------------------------------------

题解 ↑↑↑

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;
LL n, k;
LL nl[N];
LL ans;

LL binarySearch()
{
    int l = 0, r = nl[n - 1] + 1;
    while (l + 1 < r)
    {
        LL sum = 0;
        int mid = (l + r) >> 1; // 二分查找的中间位置
        for (int i = 0; i < n; i++)
        {
            sum += nl[i] / mid; // 计算每个元素除以中间位置的商,并累加到sum中
        }
        if (sum >= k)
        {
            l = mid; // 如果sum大于等于k,说明中间位置的值偏小,更新左边界
        }
        else
        {
            r = mid; // 否则,中间位置的值偏大,更新右边界
        }
    }
    return l;
}

int main()
{
    cin >> n >> k;

    // 输入数组nl的元素
    for (int i = 0; i < n; i++)
    {
        cin >> nl[i];
    }
	
    sort(nl, nl + n); // 对数组nl进行排序

    cout << binarySearch() << endl; // 输出结果

    return 0;
}

--------------------------------------------------------------------

还有五道题发放到以后做,也会发题解打卡的,可以点赞支持一下,学计算机还是要支持和鼓励的,加油!!!!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Pigwantofly

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值