目录
1、P2249 【深基13.例1】查找(洛谷,C++,二分法)
2、进击的奶牛(洛谷,C++,二分法)
3、[NOIP2015 提高组] 跳石头(洛谷,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一同进行),因为每位数字都算作一个字符,占用的长度难以确认
核心解题思路:二分搜索
以下为简单的二分搜索的实现
#includeusing 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循环调用内层二分法,以确保得到最早出现的位置
代码实现如下:
#includeusing 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; }
(开篇预警:本文是初学者所写,我自己对代码的简洁性都感到不满意,可以选择是否继续看,但注释很多,可读性应该不会特别差)
Farmer John 建造了一个有N(2<=N<=100000)个隔间的牛棚,这些隔间分布在一条直线上,坐标是x1, ..., xN(0<=xi<=1000000000)
他的C(2<=C<=N) 头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John 想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢?
第 1 行:两个用空格隔开的数字N和C。
第 2 ~ N+1 行:每行一个整数,表示每个隔间的坐标。
输出只有一行,即相邻两头牛最大的最近距离。
5 3 1 2 8 4 9
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; }
一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有N块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M块岩石(不能移走起点和终点的岩石)。
第一行包含三个整数L, M, N,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证L>=1且N >= M >= 0。
接下来 N 行,每行一个整数,第 i 行的整数 D_i( 0 < D_i < L), 表示第 i 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
一个整数,即最短跳跃距离的最大值。
25 5 2 2 11 14 17 21
4
将与起点距离为 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; }