农夫和奶牛 --二分查找的应用

农夫和奶牛就是 – P1824 进击的奶牛

初次见这种题可能不太好理解,我们一步步来,先看一个类似的题目

一.洛谷 P2678 [NOIP2015 提高组] 跳石头

题目描述:

题目背景

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

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

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

第一行包含三个整数 L,N,ML,N,ML,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L≥1L \geq 1L1 且 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 说明:将与起点距离为 222141414 的两个岩石移走后,最短的跳跃距离为 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;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
农夫过河问题是经典的逻辑与位运算应用问题。在这个问题中,农夫需要将一只狼、一只羊和一棵白菜一起过河,但是农夫只能携带一种物品并且不能让狼吃了羊或者羊吃了白菜。我们可以使用位运算来解决这个问题。 首先,我们选择一个合适的数据结构来表示农夫、狼、羊和白菜的位置。我们可以使用一个四位二进制数,其中每一位代表一个物品的位置,例如农夫在第一位,狼在第二位,羊在第三位,白菜在第四位。 接下来,我们使用位运算来判断一个特定状态是否合法。首先,我们需要判断农夫是否在狼和羊、羊和白菜的同一侧。我们可以使用异或运算符(^)来实现这个判断。如果农夫和狼(或羊)在同一侧,结果为0;如果农夫和狼(或羊)不在同一侧,结果为1。如果任意两者结果都是0,表示这个状态是合法的。 然后,我们需要判断是否有非法状态,即农夫不在场的情况。我们可以使用位掩码(与运算符(&))来判断某个物品是否在特定位置。如果运算结果为0,表示这个物品不在指定位置。 基于以上分析,我们可以使用逻辑与位运算来解决农夫过河问题。我们从一个初始状态开始,不断生成下一个合法状态,直到找到目标状态。在整个过程中,我们需要使用位运算来判断状态的合法性。 总结来说,位运算在农夫过河问题中的应用是判断状态的合法性。通过使用位运算来判断农夫、狼、羊和白菜的位置关系,我们可以有效地解决这个问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值