(Week 2)二分法(C++)

目录

        1、P2249 【深基13.例1】查找(洛谷,C++,二分法)        

        2、进击的奶牛(洛谷,C++,二分法)

        3、[NOIP2015 提高组] 跳石头(洛谷,C++,二分法)


P2249 【深基13.例1】查找(洛谷,C++,二分法)

题目描述

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

输入格式

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

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

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

输出格式

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

输入样例

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

输出样例

1 2 -1 

说明/提示

数据保证,1 <= n <= 10^6,0 <= ai,q <= 10^9,1 <= m <= 10^5

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

注:虽然这里要使用较快的IO方式,但是不能通过getline(cin, str)而只能通过减少I/O次数(如Input与Processed一同进行),因为每位数字都算作一个字符,占用的长度难以确认

解题思路:

核心解题思路:二分搜索

以下为简单的二分搜索的实现

#include 
using namespace std;
​
int main()
{
    int num_array[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int right = 9, left = 0, middle;
    int acquire;
    bool acquire_result = false;
    cin >> acquire;
    while (right >= left)
    {
        middle = (right + left) / 2;
        if (num_array[middle] == acquire)
        {
            acquire = true;
            break;
        }
        else if (num_array[middle] > acquire)
        {
            right = middle - 1;
        }
        else
        {
            left = middle + 1;
        }
    }
    if (acquire_result)
    {
        cout << "True" << endl;
    }
    else
    {
        cout << "False" << endl;
    }
    return 0;
}

采用IPO思路实现代码:

(1)I:

创建两个数组,分别用于存储n个数字和m次询问的结果

用for循环输入n个数字

(2)P和O:

用for循环询问m次,每次得到的结果存储到数组中,统一输出

每次循环键入一个值,用二分法搜索

搜索到指定值以后,再次用while循环调用内层二分法,以确保得到最早出现的位置

代码实现如下:

#include 
using namespace std;
​
//考虑到边界条件,前后各留出一个冗余位
int num_array[int(1e6) + 1 + 1] = { -1 };//n个数字
int num_array_q[int(1e5) + 1] = { -1 };//m次询问结果
​
int main()
{
    int n, m;
    int i, j, z, middle;//循环和二分控制符
    int pos;//获取最早出现位置,若未搜索到,则为-1
    std::cin.tie(nullptr);
    cin >> n >> m;
    for (i = 1; i <= n; i++)//循环输入n个数字
    {
        cin >> num_array[i];
    }
    for (z = 1; z <= m; z++)//循环进行m次询问
    {
        cin >> num_array_q[z];
        //二分搜索开始
        i = 1, j = n, pos = -1;
        while (i <= j)
        {
            middle = (i + j) / 2;
            if (num_array[middle] == num_array_q[z])
            {
                while (num_array_q[z] == num_array[middle - 1])//while循环条件:只要middle的左侧仍有相同元素,就继续二分查找
                {
                    int inner_j = middle - 1;//内层二分的右
                    int inner_middle;//内层二分的middle
                    while (i <= inner_j)
                    {
                        inner_middle = (i + inner_j) / 2;
                        if (num_array[inner_middle] == num_array_q[z])
                        {
                            middle = inner_middle;
                            break;//找到内层middle,赋值给外层middle,退出,进入下一轮内层二分
                        }
                        else if (num_array[inner_middle] > num_array_q[z])
                        {
                            inner_j = inner_middle - 1;
                        }
                        else
                        {
                            i = inner_middle + 1;
                        }
                    }
                }
                pos = middle;
                break;
            }
            else if (num_array[middle] > num_array_q[z])
            {
                j = middle - 1;
            }
            else
            {
                i = middle + 1;
            }
        }
        cout << pos << ' ';
    }
    return 0;
}

进击的奶牛(洛谷,C++,二分法)

(开篇预警:本文是初学者所写,我自己对代码的简洁性都感到不满意,可以选择是否继续看,但注释很多,可读性应该不会特别差)

题目描述

Farmer John 建造了一个有N(2<=N<=100000)个隔间的牛棚,这些隔间分布在一条直线上,坐标是x1, ..., xN(0<=xi<=1000000000)

他的C(2<=C<=N) 头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John 想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢?

输入格式

第 1 行:两个用空格隔开的数字N和C。

第 2 ~ N+1 行:每行一个整数,表示每个隔间的坐标。

输出格式

输出只有一行,即相邻两头牛最大的最近距离。

样例 #1

样例输入 #1

5 3
1 
2 
8 
4 
9

样例输出 #1

3

解题思路:

核心解题思路1:由于目标值(最短距离的最大值)有范围限制,故用二分法搜索

范围限制:牛之间的间隔要尽可能的大,但间隔越大,所需要的牛棚数目越多,而牛棚数目是有限的

i = 1, j = integer_vector[N - 1];
    while (i <= j)//二分法寻找最大距离
    {
        middle = (i + j) / 2;
        index = is_accommodate(middle);
        if (index == 0)//容纳不下
        {
            j = middle - 1;
        }
        else if (index == 2)//位置剩余
        {
            i = middle + 1;
        }
        else//刚好可以(未必找到最大距离,可能有多个距离刚好可以,故不一定可以找到最大的最小距离)
        {
            while (is_accommodate(middle))//用以确保找到最大最小距离
            {
                middle++;
            }
            break;
        }
        //5 5
        //10 30 31 52 73 94
        //这组样例对于20和21均“刚好可以”
        //如果先尝试的是20,则max_min_dist为20,反之,则为21,而只有21才能符合题意
    }

核心解题思路2:将一头牛放在第一个牛棚,然后来到第二个牛棚,检查是否符合distance,符合则再放入一头牛,不符合则前往第三个牛棚,依次类推

int is_accommodate(int distance)//检测distance所符合的情形:0为空间不足,1为恰好容纳,2为空间冗余
{
    temp_max_min_dist = 1e9;//对于每个distance,都应该初始化temp_max_min_dist
    int i;//循环控制符
    //第一个位置直接放入一头牛
    int sum_dist = -1 * integer_vector[0];//用于判断是否>=distance
    int temp_c = C - 1;//用于判断是否可以容纳
    for (i = 2; i <= N; i++)//从第二个位置开始遍历
    {
        if (sum_dist + integer_vector[i - 1] >= distance)//判断是否符合距离条件
        {
            temp_c--;//放入一头牛
            if (temp_max_min_dist > sum_dist + integer_vector[i - 1])//寻找本轮的临时最大最小距离
            {
                temp_max_min_dist = sum_dist + integer_vector[i - 1];
            }
            sum_dist = -1 * integer_vector[i - 1];//放入一头牛后,再次初始化
            if (temp_c == 0)//牛全部放入,退出循环
            {
                break;
            }
        }
    }
    //退出循环只有两种情形:一是temp_c == 0,二是i == N + 1退出
    if (temp_c == 0)//找到牛可以全部放入的一种情形
    {
        max_min_dist = temp_max_min_dist;//不进行任何判断直接赋值是因为:越是靠后出现的值,就是越接近要求的值
        if (i == N)//空间刚好用尽
        {
            return 1;
        }
        else//空间冗余
        {
            return 2;
        }
    }
    else//空间不足
    {
        return 0;
    }
}

核心解题思路3:既然要从第一个牛棚开始依次遍历,那么所有牛棚的坐标就要有顺序(直接用sort()的话,可以直接跳过这一条),故需要排序算法,这里仍然采用二分法,搜索并插入,每键入一个坐标就要搜索和插入一次,因为二分法搜索的对象一定是有序的

注:由于需要频繁的插入的同时支持随机访问,故选择了vector容器

//键入坐标,同时进行二分法排序(升序)
    //前两个个元素单独处理
    cin >> temp_distance;
    integer_vector.push_back(temp_distance);
    cin >> temp_distance;
    if (temp_distance >= integer_vector[0])
    {
        integer_vector.push_back(temp_distance);
    }
    else
    {
        integer_vector.insert(integer_vector.begin(), temp_distance);
    }
    for (i = 3; i <= N; i++)
    {
        cin >> temp_distance;
        //确保temp_distance在二分查找的范围内
        if (temp_distance >= integer_vector[integer_vector.size() - 1])
        {
            integer_vector.push_back(temp_distance);
            continue;
        }
        else if (temp_distance <= integer_vector[0])
        {
            integer_vector.insert(integer_vector.begin(), temp_distance);
            continue;
        }
        index = false;//初始化开关状态
        left = 0, right = integer_vector.size() - 1;//初始化左和右
        while (right + 1 != left)//左和右不相邻,则继续
        {
            middle = (right + left) / 2;
            if (integer_vector[middle] == temp_distance)
            {
                integer_vector.insert(integer_vector.begin() + middle, temp_distance);
                index = true;//开关状态改变
                break;
            }
            else if (integer_vector[middle] > temp_distance)
            {
                right = middle - 1;
            }
            else
            {
                left = middle + 1;
            }
        }
        if (!index)
        {
            integer_vector.insert(integer_vector.begin() + left, temp_distance);
        }
    }
    //for (vector::iterator it = integer_vector.begin(); it != integer_vector.end(); it++)
    //{
    //  cout << *it << endl;
    //}//输出排序结果测试

完整的代码实现如下:

#include 
#include 
using namespace std;
​
int N, C;//牛棚数目、牛的数目
int max_min_dist = 1e9;//最终的最小距离的最大值
int temp_max_min_dist = 0;//每轮最小距离的最大值
vector integer_vector;//用于存储键入的牛棚坐标
​
int is_accommodate(int distance)//检测distance所符合的情形:0为空间不足,1为恰好容纳,2为空间冗余
{
    temp_max_min_dist = 1e9;//对于每个distance,都应该初始化temp_max_min_dist
    int i;//循环控制符
    //第一个位置直接放入一头牛
    int sum_dist = -1 * integer_vector[0];//用于判断是否>=distance
    int temp_c = C - 1;//用于判断是否可以容纳
    for (i = 2; i <= N; i++)//从第二个位置开始遍历
    {
        if (sum_dist + integer_vector[i - 1] >= distance)//判断是否符合距离条件
        {
            temp_c--;//放入一头牛
            if (temp_max_min_dist > sum_dist + integer_vector[i - 1])//寻找本轮的临时最大最小距离
            {
                temp_max_min_dist = sum_dist + integer_vector[i - 1];
            }
            sum_dist = -1 * integer_vector[i - 1];//放入一头牛后,再次初始化
            if (temp_c == 0)//牛全部放入,退出循环
            {
                break;
            }
        }
    }
    //退出循环只有两种情形:一是temp_c == 0,二是i == N + 1退出
    if (temp_c == 0)//找到牛可以全部放入的一种情形
    {
        max_min_dist = temp_max_min_dist;//不进行任何判断直接赋值是因为:越是靠后出现的值,就是越接近要求的值
        if (i == N)//空间刚好用尽
        {
            return 1;
        }
        else//空间冗余
        {
            return 2;
        }
    }
    else//空间不足
    {
        return 0;
    }
}
​
int main()
{
    int i, j;//循环控制符
    int index;//分支控制开关
    int temp_distance;//用于读入距离
    cin >> N >> C;//键入牛棚和牛的数量
    integer_vector.reserve(N);//预留capacity,防止反复开辟浪费时间
    int right, left, middle;//用于二分操作的右和左和中
    //键入坐标,同时进行二分法排序(升序)
    //前两个个元素单独处理
    cin >> temp_distance;
    integer_vector.push_back(temp_distance);
    cin >> temp_distance;
    if (temp_distance >= integer_vector[0])
    {
        integer_vector.push_back(temp_distance);
    }
    else
    {
        integer_vector.insert(integer_vector.begin(), temp_distance);
    }
    for (i = 3; i <= N; i++)
    {
        cin >> temp_distance;
        //确保temp_distance在二分查找的范围内
        if (temp_distance >= integer_vector[integer_vector.size() - 1])
        {
            integer_vector.push_back(temp_distance);
            continue;
        }
        else if (temp_distance <= integer_vector[0])
        {
            integer_vector.insert(integer_vector.begin(), temp_distance);
            continue;
        }
        index = false;//初始化开关状态
        left = 0, right = integer_vector.size() - 1;//初始化左和右
        while (right + 1 != left)//左和右不相邻,则继续
        {
            middle = (right + left) / 2;
            if (integer_vector[middle] == temp_distance)
            {
                integer_vector.insert(integer_vector.begin() + middle, temp_distance);
                index = true;//开关状态改变
                break;
            }
            else if (integer_vector[middle] > temp_distance)
            {
                right = middle - 1;
            }
            else
            {
                left = middle + 1;
            }
        }
        if (!index)
        {
            integer_vector.insert(integer_vector.begin() + left, temp_distance);
        }
    }
    //for (vector::iterator it = integer_vector.begin(); it != integer_vector.end(); it++)
    //{
    //  cout << *it << endl;
    //}//输出排序结果测试
    i = 1, j = integer_vector[N - 1];
    while (i <= j)//二分法寻找最大距离
    {
        middle = (i + j) / 2;
        index = is_accommodate(middle);
        if (index == 0)//容纳不下
        {
            j = middle - 1;
        }
        else if (index == 2)//位置剩余
        {
            i = middle + 1;
        }
        else//刚好可以(未必找到最大距离,可能有多个距离刚好可以,故不一定可以找到最大的最小距离)
        {
            while (is_accommodate(middle))//用以确保找到最大最小距离
            {
                middle++;
            }
            break;
        }
        //5 5
        //10 30 31 52 73 94
        //这组样例对于20和21均“刚好可以”
        //如果先尝试的是20,则max_min_dist为20,反之,则为21,而只有21才能符合题意
    }
    cout << max_min_dist << endl;
    return 0;
}

[NOIP2015 提高组] 跳石头(洛谷,C++,二分法)

题目背景

一年一度的“跳石头”比赛又要开始了!

题目描述

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有N块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M块岩石(不能移走起点和终点的岩石)。

输入格式

第一行包含三个整数L, M, N,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证L>=1且N >= M >= 0。

接下来 N 行,每行一个整数,第 i 行的整数 D_i( 0 < D_i < L), 表示第 i 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出格式

一个整数,即最短跳跃距离的最大值。

样例 #1

样例输入 #1

25 5 2 
2
11
14
17 
21

样例输出 #1

4

提示

输入输出样例 1 说明

将与起点距离为 2和 14 的两个岩石移走后,最短的跳跃距离为 4(从与起点距离 17 的岩石跳到距离 21 的岩石,或者从距离 21 的岩石跳到终点)。

数据规模与约定

对于 20% 的数据,0 <= M <= N <= 10。 对于 50% 的数据,0 <= M <= N <= 100。 对于 100%的数据,0 <= M <= N <= 50000,1 <= L <= 10^9。

解题思路:

核心解题思路1:因为目标值(最短跳跃距离的最大值)有一个限定范围,故可以采用二分法搜索

限定范围:distance要尽可能的大,但distance越大,移除石头数目就越多,而移除石头的数目又不能超过M

//二分法(红蓝)
    int left = 0, middle, right = L + 1;
    while (left + 1 != right)
    {
        middle = (left + right) / 2;
        if (judge(middle))//返回值为真,继续尝试更大距离
        {
            ans = middle;
            left = middle;
        }
        else//返回值为假,需要缩短距离
        {
            right = middle;
        }
    }

核心解题思路2:站在起点上,观察下一块石头,若该石头不符合distance,则移除,否则站在这块石头上,再观察下一块石头,以此类推

bool judge(int distance)
{
    int sum = 0;
    int head = stone_array[0];
    for (int step = 1; step <= N; step++)//终点石头不能移动,故最后step到N即可
    {
        if (stone_array[step] - head < distance)
        {
            sum++;
        }
        else
        {
            head = stone_array[step];
        }
    }
    if (sum <= M)
    {
        return true;
    }
    else
    {
        return false;
    }
}
​
​

但这段代码只能符合两个条件中的一个,第二个条件不能满足,是因为最后第N块石头到终点的距离无法保证符合distance

解决方法就是加上一个简单的判断

while (L - stone_array[temp_N] < distance)//只要第N块石头到终点的距离不符合distance,就temp_N--
    {
        sum++;
        temp_N--;
    }

最后,完整代码如下:

#include 
#include 
using namespace std;
​
int L, N, M;//分别表示起点到终点的距离(终点坐标),起点和终点之间的岩石数,以及组委会至多移走的岩石数
int stone_array[50000 + 2];
​
bool judge(int distance)
{
    int temp_N = N;
    int sum = 0;
    int head = stone_array[0];
    while (L - stone_array[temp_N] < distance)
    {
        sum++;
        temp_N--;
    }
    for (int step = 1; step <= temp_N; step++)
    {
        if (stone_array[step] - head < distance)
        {
            sum++;
        }
        else
        {
            head = stone_array[step];
        }
    }
    if (sum <= M)
    {
        return true;
    }
    else
    {
        return false;
    }
}
​
int main()
{
    int ans = 0;
    cin >> L >> N >> M;
    stone_array[0] = 0;
    for (int i = 1; i <= N; i++)
    {
        cin >> stone_array[i];
    }
    stone_array[N + 1] = L;
    //二分法(红蓝)
    int left = 0, middle, right = L + 1;
    while (left + 1 != right)
    {
        middle = (left + right) / 2;
        if (judge(middle))
        {
            ans = middle;
            left = middle;
        }
        else
        {
            right = middle;
        }
    }
    cout << ans << endl;
    return 0;
}

你可能感兴趣的:(NEUQACM作业,c++,算法)