农夫和奶牛就是 – P1824 进击的奶牛
初次见这种题可能不太好理解,我们一步步来,先看一个类似的题目
一.洛谷 P2678 [NOIP2015 提高组] 跳石头
题目描述:
题目背景
一年一度的“跳石头”比赛又要开始了!
题目描述
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 NNN 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 MMM 块岩石(不能移走起点和终点的岩石)。
输入格式
第一行包含三个整数 L,N,ML,N,ML,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L≥1L \geq 1L≥1 且 N≥M≥0N \geq M \geq 0N≥M≥0。
接下来 NNN 行,每行一个整数,第 iii 行的整数 Di(0<Di<L)D_i( 0 < D_i < L)Di(0<Di<L), 表示第 iii 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出格式
一个整数,即最短跳跃距离的最大值。
输入输出样例
输入 #1
25 5 2
2
11
14
17
21
输出 #1
4
说明/提示
输入输出样例 1 说明:将与起点距离为 222和 141414 的两个岩石移走后,最短的跳跃距离为 444(从与起点距离 171717 的岩石跳到距离 212121 的岩石,或者从距离 212121 的岩石跳到终点)。
另:对于 20%20\%20%的数据,0≤M≤N≤100 ≤ M ≤ N ≤ 100≤M≤N≤10。
对于50%50\%50%的数据,0≤M≤N≤1000 ≤ M ≤ N ≤ 1000≤M≤N≤100。
对于 100%100\%100%的数据,0≤M≤N≤50,000,1≤L≤1,000,000,0000 ≤ M ≤ N ≤ 50,000,1 ≤ L ≤ 1,000,000,0000≤M≤N≤50,000,1≤L≤1,000,000,000。
思路:
首先声明你一定能理解这题hah
初次看见这种题,我一脸懵逼,什么最短最大值,最大最小值???其实也不难理解,我们可以这样理解,就是有多个解(即最短距离)可以满足我搬移的石头少于m个,但是我们需要最大的,因为最小的显然没有意义,我们如果规定最短距离为1,显然也符合要求。
按照这样的思路我们就可以采用枚举的方法,因为这个最短距离是有区间的即 1 ~ len 所以我们可以尝试每一个最后一个成功符合要求的就是最大最短距离了。但是我们尝试显然不能枚举,那样的话我们就会超时,这时二分查找的作用就凸现出来了。
!!!!最重要的地方来了
我们的isValid函数该怎么写?我们判断当前枚举的这个距离是所有跳跃距离中最短的,显然两个石头之间的距离不能小于这个距离此时我们就需要搬移这个石头,但是题目有限制最多搬移m个石头。我们每次函数中计算搬移的石头和m比较即可判断是否合法!
题解:
#include <iostream>
#define maxn 500005
using namespace std;
int pos[maxn];
int ans;
// len长度,n为石头个数,m为限制最多去掉石头的个数
int len, n, m;
bool isValid(int minDis) {
// tone 移走的石头总数, i当前判断的石头,loc我们所站立的石头
int tone = 0, loc = 0;
// 判断每块石头是否合法
for(int i=1; i<=n+1; i++) {
// 非法解,移走石头
if (pos[i]-pos[loc] < minDis) {
tone++;
} else {
// 当前位置改为i
loc = i;
}
}
// 比较tone总数和m
if (tone > m) return false;
else return true;
}
int main() {
cin >> len >> n >> m;
for (int i=1; i<=n; i++) {
cin >> pos[i];
}
pos[n+1] = len;
// 终点和起点的位置
int left = 1, right = len;
while (left <= right) {
int mid = left - (left-right)/2;
if (isValid(mid)) {
// 合法记录答案,并且在右边找更大的解
ans = mid;
left = mid + 1;
} else {
// 不合法就在左边找合法解
right = mid - 1;
}
}
cout << ans << endl;
return 0;
}
二.洛谷P1824 进击的奶牛
题目描述:
Farmer John建造了一个有N(2<=N<=100,000)个隔间的牛棚,这些隔间分布在一条直线上,坐标是x1,...,xN (0<=xi<=1,000,000,000)。
他的C(2<=C<=N)头牛不满于隔间的位置分布,它们为牛棚里其他的牛的存在而愤怒。为了防止牛之间的互相打斗,Farmer John想把这些牛安置在指定的隔间,所有牛中相邻两头的最近距离越大越好。那么,这个最大的最近距离是多少呢?
输入格式
第1行:两个用空格隔开的数字N和C。
第2~N+1行:每行一个整数,表示每个隔间的坐标。
输出格式
输出只有一行,即相邻两头牛最大的最近距离。
输入输出样例
输入 #1
5 3
1
2
8
4
9
输出 #1
3
思路:
几乎和跳石头完全相同,只是我们跳石头不包括起点和终点,我们这里的房间要从第一个开始,我们直接把room设为1(因为我们第一次判断位置可用的话就是两个房间,后面可用的房间每次++)更新loc是当我们判断当前房间可用的时候,此时判断下一个房间是否可用,并且更新当前位置 最后我们跟C比较,看房间是否够用。值得注意的是我们需要将坐标排序
题解:
#include <iostream>
#include <algorithm>
#define maxn 100005
using namespace std;
int pos[maxn];
int ans;
int n, c;
bool isValid(int minDis) {
// room 为按照当前距离所能使用的房间数量, loc表示当前位置
int room = 1, loc = 0;
// i为当前尝试的房间
for (int i=1; i<n; i++) {
if (pos[i] - pos[loc] >= minDis) {
room++;
loc = i;
}
}
if (room >= c) return true;
else return false;
}
int main() {
// n个隔间 c头奶牛
cin >> n >> c;
for (int i=0; i<n; i++) {
cin >> pos[i];
}
sort(pos,pos+n);
int left = 1, right = 999999999;
while (left <= right) {
int mid = left - (left-right)/2;
if (isValid(mid)) {
// 到右边找更大的值
left = mid + 1;
ans = mid;
} else {
// 左边找可行解
right = mid - 1;
}
}
cout << ans << endl;
return 0;
}