Binary Search

1.数组二分查找

查找数组中第一个等于8的元素的下标

查找第一个2

1 2 2 2 3 3 

三种区间缩小方法

#include <bits/stdc++.h>
using namespace std;

int a[6] = {1, 2, 2, 2, 3, 3};

int main(){
	
	// l r 可开可闭
	int l = -1, r = 6;
	while( l<=r ){ 
		int mid = (l+r)>>1;
		if( a[mid]>=2 ){
			r = mid-1;
		}
		else{   
			l = mid+1;
		}
	}
	cout << l << endl;
	
	l = 0;
	r = 5;
	// 不能r=mid-1 l=mid
	// 退出循环(l==r)前一次是l+1==r==mid
	while( l<r ){
		int mid = (l+r)>>1;
		if( a[mid]>=2 ){
			r = mid; 
		}
		else{
			l = mid+1;
		}
	}
	cout << r << endl;
	cout << l << endl;
	
	l = 0;
	r = 5;
	int mid;
	while( l+1!=r ){
		mid = (l+r)>>1;
		if( a[mid]>=2 ){
			r = mid;
		}
		else{
			l = mid;
		}
	}
	cout << mid << endl;
}

2.二分查找找答案

答案是有范围的[min, max](难点通常在找哪个值的范围来进行二分)

获得中间值mid = (min + max) >> 1,判断mid是不是符合题意。如果符合就缩小范围。

【考点 二分和贪心一起出】check函数用贪心

二分+贪心,相邻距离可以通过二分查找,然后用贪心思路检测该距离是否放得下c头牛。贪心的方法就是,第一个框必放,每达到或超过检测距离也放。 即实际一次摆放间距超过检测距离是可以的,只要保证所有间距里最小的那个是检测距离就行。

#include <bits/stdc++.h>
using namespace std;

int n,c;
int a[100005];

bool check(int x){
	// 贪心
	int cnt = c;
	// 第一个必须放牛
	cnt--;
	int pre = a[0];
	for(int i=1; i<n; i++){
		if( a[i]-pre==x ){
			pre = a[i];
			cnt--;
		}
		else if( a[i]-pre>x ){ // 超过了 在该框放
			pre = a[i];
			cnt--;
		}
		else{  // 跳过该框继续找下一个
			continue;
		}
	}
	
	if( cnt<=0 ){
		return true;
	}
	else{
		return false;
	}
}

int main(){
	scanf("%d %d", &n, &c);
	for(int i=0; i<n; i++){
		scanf("%d", &a[i]);
	}
	sort(a, a+n); // 左闭右开 默认从小到大
	// 距离在[0, a[n-1]-a[0]]之间
	int l = 0;
	int r = a[n-1] - a[0];
	while( l<=r ){
		int mid = (l+r)>>1;
		if( check(mid) ){ // 这个间距还能放下 继续放大间距
			l = mid+1;
		}
		else{
			r = mid-1;
		}
	}
	cout << r << endl; 
// 最后一次循环一定会执行l=mid+1 所以应该输出上一次循环的答案l==r==mid
}

A soldier's task 【最小行进距离】

【思路】N+1(N个要输入的城市坐标,一个起点坐标0)个城市,要在K天内走完,问每天最少走多少。要到达第N个城市,说明N城市必须休息。

同样的二分+贪心 ,每天行进距离可以二分查找,如果check可以在k天走完,就继续缩小距离。在check函数中,不同于上一道题实际出现的间距可以大于检测值,本道题每次行进距离必须小于等于检测值,这就导致在遍历坐标上的点时处理应当不同。

#include <bits/stdc++.h>
using namespace std;

long long A[100001];
int N, K;
// 每天走的路程在[1, 10^18]之间 可以二分找答案
// check函数 判断是否可以在K天内走完
// check结果为true 每日行走路程向左减少 否则 向右边增加
int check(long long x){
	int cnt = 0;
	long long need = 0;
	for(int i = 1; i < N + 1;i++) {
		if(A[i] - A[i - 1] > x) return 0;
		need += A[i] - A[i - 1];
		if(need > x){
			need = A[i] - A[i - 1];
			cnt++;
		}
	}
	if(need != 0) cnt++;
	if(cnt <= K)
		return 1;
	else
		return 0;
}

long long BSearch(long long left, long long right){
	long long x=A[N];
	while( left<=right ){
		long long mid = (left+right)>>1;
		if( check(mid) ){
			x = min(x, mid);
			right = mid-1;
		}
		else{
			left = mid+1;
		}
	}
	return x;
}

int main(){
	while(~scanf("%d %d", &N, &K)){
		A[0] = 0;
		for(int i=1; i<N+1; i++){
			scanf("%lld", &A[i]);
		}
		printf("%lld\n", BSearch(1, A[N]-A[0]));
	}
}

贪心部分仿照奶牛间隔那道题写的,能过样例。 a不ac不知道。

#include <bits/stdc++.h>
using namespace std;

int n,k;
long long a[100005];

bool check(long long x){
	int day = 0;
	long long pre = a[0];
	long long dis = 0; // 上次休息到当前城市的距离
	for(int i=1; i<=n; i++){
		if( a[i]-pre>=x ){
			if( a[i]-a[i-1]>x ) return false;
			
			dis += a[i]-pre;
			if( dis>x ){
				pre = a[i-1];
				day++;
				dis = a[i]-a[i-1];
			}
			
		}
		else{
			dis+=a[i]-pre;
			if( dis>x ){
				pre = a[i];
				day++;
				dis = 0;
			}
			continue;
		}

	}
	if( pre!=a[n] ){
		day++;
	}
	
	if( day<=k ){
		return true;
	}
	else{
		return false;
	}
}

int main(){
	while( scanf("%d %d", &n, &k)!=EOF ){
		a[0] = 0;
		for(int i=1; i<=n; i++){
			scanf("%lld", &a[i]);
		}
		
		long long l = 0;
		long long r = a[n]-a[0];
		while( l<=r ){
			long long mid = (l+r)>>1;
			if( check(mid) ){
				r = mid-1;
			}
			else{
				l = mid+1;
			}
		}
		
		printf("%lld\n", l);
	}
}

 714抄书
在印刷术发明之前,复印一本书是非常困难的。所有的内容都有
由所谓的抄写员用手重写。抄写员拿到了一本书,几本书之后
几个月后,他完成了副本。加快速度的唯一方法就是雇佣更多的抄写员。

所以他们雇了很多抄写员来抄写这些书。假设你有m本书(编号)
1, 2,…, m),可能有不同页数(p1, p2,…), pm),你要复印一份
每一个。你的任务是把这些书分配给k个抄写员,k≤m。每本书都可以分配
只给一个抄写员,每个抄写员必须得到连续的图书序列。这意味着
存在0 = b0 < b1 < b2,…的递增数列。
所有的书都是由分配工作最多的抄写员决定的。因此,我们的目标是
尽量减少分配给单个抄写员的最大页数。你的任务是找到最优的
任务。
输入
输入包含N个case。输入的第一行只包含正整数n
的病例。每个case恰好由两行组成。在第一行,有两个整数m和k,
1≤k≤m≤500。在第二行,有整数p1, p2,…, PM用空格分隔。所有这些
取值为正数且小于10000000。
输出
对于每种情况,只打印一行。该行必须包含连续输入p1, p2,…。点分
精确地分成k个部分使得单个部分的最大和尽可能小。使用
分隔部分的斜杠字符(' / ')。任何字符之间必须有一个空格字符
两个连续的数字,在数字和斜杠之间。
如果有多个解决方案,打印分配给第一个抄写员的工作量最少的那个;
然后是第二个抄写员等等。但是每个抄写员必须至少分配一本书。

Sample Input
2
9 3
100 200 300 400 500 600 700 800 900
5 4
100 100 100 100 100
Sample Output
100 200 300 400 500 / 600 700 / 800 900
100 / 100 / 100 / 100 100

没做分配部分,只写了 二分查找最小页数的部分

#include <bits/stdc++.h>
using namespace std;

int n,m,k;
long long a[505];
bool check(long long x){
	int cnt = 1; // 给第一个人分配 下一次cnt++是给第二个人分配的
	long long cur = 0;
	for(int i=0; i<m; i++){
		if( cur+a[i]<=x ){
			cur += a[i];
		}
		else if( cur+a[i]>x ){
			cur = a[i]; // 从i-1处断开
			cnt++;
		}
		
	}
	if( cnt<=k ){
		return true;
	}
	else{
		return false;
	}
}

int main(){
	scanf("%d", &n);
	while(n--){
		scanf("%d %d", &m, &k);
		long long sum=0;
		for(int i=0; i<m; i++){
			scanf("%lld", &a[i]);
			sum+=a[i];
		}
		// 每个人写的页数从[a[m-1], sum]
		long long l = a[m-1];
		long long r = sum;
		long long ans = 0;
		while( l<=r ){
			long long mid = (l+r)>>1;
			if( check(mid) ){
				ans = r;
				r = mid-1;	
			}
			else{
				l  =mid+1;
			}
		}
		printf("%lld\n", l);
	}
}

Sequence transformation

描述

Given sequence A={A1,A2,...,An}, it is required to change some elements in sequence A to form a strictly monotonic sequence B (strictly monotonic is defined as: Bi<Bi+1,for 1<=i<N).

We define the cost of the transformation from sequence A to sequence B as cost(A,B)=max(|Ai−Bi|)(1≤i≤N). Please calculate the minimum cost for the transformation.

Note that each element is an integer before and after the transformation.

输入

The input contains multiple test cases. Each test case contains an integer N (0<N<10^5), the length of the sequence. The next line are N integers a1, a2, ... , an (0<ai<10^6, for 1<=i<=N). The input terminates at the end of the file (EOF).

输出

For each test case, print the minimum cost.

输入样例 1 

2

1 10

3

2 5 4

输出样例 1

0

1

提示

Binary Search

For the second case: [2, 5, 4] -> [2, 4, 5] or [3, 4, 5]

#include <bits/stdc++.h>
using namespace std;

int A[100001];
int N;
// 把B序列看作A序列的变换 不另外开辟数组
// 每个数都可以在一定范围内变化,使A序列变为递增的序列
// cost就是某个数变化范围的最小值
// Ai=[0, 10^6]所以可以在0~10^6范围内通过二分找答案
// check函数 判断下一次二分是在mid之前还是之后 运用贪心思想
int check(int x){
	int pre = A[0]-x; // 此时pre相当于B[0]
	
	for(int i=1; i<N; i++){
		// 遍历A序列 判断Ai-x或Ai+x是否满足递增序列的要求
		// 分类讨论
		if( A[i]-x > pre ){ // Ai变得最小仍满足递增序列
			pre = A[i]-x;
			continue;
		}
		else if( A[i]+x <= pre ){ // Ai最大为Ai+x 仍不能比序列前面的数字大 x不能作为最小变化范围
			return 0;
		}
		else if( A[i]-x<=pre && A[i]+x>pre ){
			pre++;
			continue;
		}
	}
	return 1;
}

int BSearch(int left, int right){
	int x;
	while( left <= right ){
		int mid = (left+right)>>1;
		if(check(mid)){
			// mid可以作为变化差值 因为要找最小的 所以向左边继续二分
			x = mid;
			right = mid-1;	
		} 
		else
			left = mid+1;
	}
	return x;
}

int main(){
	while(scanf("%d", &N)!=EOF){
		for(int i=0; i<N; i++){
			scanf("%d", &A[i]);
		}
		printf("%d\n", BSearch(0, 1000000));
	}
	
}

Buying Traffic(购买流量)

描述

On the way to City Fun, Ming wants to download a mobile game. Ming is choosing which game to download when a message pops up on his phone telling him that he has run out of traffic (流量). Since the trip is so long, Ming decides to buy some traffic packs (流量包). Each traffic pack can be bought only once. Ming wants to know the minimum number of traffic packs he needs to buy for each game.

输入

The first line contains two integers n and q (1<=n,q<=100000), representing the number of traffic packs and the number of games respectively (分别地). The second line contains n integers, and the ith integer represents the amount of traffic Ai in the ith traffic pack (1<=Ai<=10^9). The third line contains q integers, and the ith integer represents the amount of traffic used by the ith game xi (1<=xi<=10^18).

输出

For the ith line, output an integer W representing the number of traffic packs that Ming needs to buy for the ith game.

输入样例 1 

3 3
5 4 3 // 每个流量包的值
5 6 10 // 每个游戏需要的流量值 输出它们需要的流量包个数

输出样例 1

1
2
3

提示

15分

Binary Search & Prefix Sum (前缀和)

#include <bits/stdc++.h>
using namespace std;

int n,q;
long long a[100005];
long long x[100005];
long long sum[100005];

void search(long long x){
	int l = 0;
	int r = n-1;
	while( l<=r ){
		int mid = (l + r)>>1;
		if( x<=sum[mid] ){
			r = mid-1;
		}
		else{// x>sum[mid]
			l = mid + 1;
		}
	}
	printf("%d\n", l);
}
bool compare(int a, int b) {
	return a > b;  // 返回a > b时,表示按照从大到小的顺序排序
}

int main(){
	scanf("%d %d", &n, &q);
	for(int i=1; i<=n; i++)
		scanf("%lld", &a[i]);
	
	sort(a+1, a+n+1, compare);
	
//	for(int i=0; i<n; i++){
//		printf("%lld ", a[i]);
//	}
	// sum[i]表示买i个包最大能获得的流量
	for(int i=1; i<=n; i++){
		sum[i]= sum[i-1]+a[i];
	}
	

	// 买包的数量是1~n之间
	for(int i=0; i<q; i++){
		scanf("%lld", &x[i]);
		search(x[i]);
	}
	
	
	
}

【一些难以找到二分范围的题】

Tuple (元组)

描述

Given an array, find the number of [i,j] (i and j are the indexes of the array (数组下标), not values) tuples in the array that satisfy array[i]+array[j]<=k.

输入

There are several test cases. For each test case, The first line contains two positive integers n (1<=n<=10^5) and k (1<=k<=10^18). The second line contains n integers, and the ith integer represents the value of array[i](1<= array[i]<=10^9).

输出

For each test case, output an integer representing the answer.

输入样例 1 

2 5
1 2 
3 4
3 2 1

输出样例 1

4
6

提示

For test case 1, the tuples are [1,1], [1,2], [2,1] and [2,2].

For test case 2, the tuples are [1,3], [2,2], [2,3], [3,1], [3,2] and [3,3].

15分

Binary Search

#include <bits/stdc++.h>
using namespace std;

int n;
long long k;
long long a[100005];

int main(){
	while( scanf("%d %lld", &n, &k)!=EOF ){
		for(int i=0; i<n; i++){
			scanf("%lld", &a[i]);
		}
		
		sort(a, a+n);
		
		// 对于每一个元素 能组成n个元组
		// 满足和<k的元组个数在[0, n]之间
		int cnt = 0;
		for(int i=0; i<n; i++){
			int l = 0;
			int r = n-1;
			while( l<=r ){
				int mid = (l+r)>>1;
				if( a[mid]+a[i]<=k ){
					cnt += mid-l+1;
					l = mid+1;
				}
				else{
					r = mid-1;
				}
			}
		}
		printf("%d\n", cnt);
	}
}

 

 二分+迪杰斯特拉处理最短路径(有向连通图)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m,b;
ll f[10005];
struct Edge{
	ll adjacent; // 顶点
	ll value; // 耗费血量
};
// 二维数组存城市图
vector<Edge> edge[100005];
// 用优先队列优化dij算法找最大边的过程
struct Node{
	ll a;
	ll dis;
};
bool operator < (Node x, Node y){
	return x.dis > y.dis;
}
priority_queue<Node> q;
 采用pair键值对 默认依照key排序 <key(到达i的最大生命值), value(城市i)>
 默认为大根堆 即top是最大值 从大到小排序
//priority_queue<pair<ll, ll>>  q;
ll dis[50005]; // 到达i的最大生命值
ll visited[10005];
// check函数采用dij算法
int check(ll x){
	if( x < f[1] ){
		return 1;
	}
	// 初始化
	for(ll i=1; i<=n; i++){
		dis[i] = 1e9;
		visited[i] = 0;
	}
	dis[1] = 0;
	while(!q.empty()) 
		q.pop();
	q.push((Node){1, dis[1]});
	while( !q.empty() ){
		ll u = q.top().a; 
		q.pop();
		if( visited[u]==1 ) continue;
		else visited[u] = 1;
		for(ll i=0; i<(int)edge[u].size(); i++){ // 遍历所有与u邻接的点
			ll v = edge[u][i].adjacent;
			if( f[v]>x ) continue;
			// 根据血量找最短路
			if( dis[v] > dis[u]+edge[u][i].value ){
				dis[v] = dis[u]+edge[u][i].value;
				q.push((Node){ v, dis[v]});
			}
		}
	}
	if( dis[n]>b ) return 1;
	else return 0;
}

int main(){
	scanf("%lld %lld %lld", &n, &m, &b);
	ll maxf = 0;
	ll minf = 1e9;
	for(ll i=1; i<=n; i++){
		scanf("%lld", &f[i]);
		maxf = max( f[i], maxf);
		minf = min( f[i], minf);
	}
	ll x, y, z;
	for(ll i=1; i<=m; i++){
		scanf("%lld %lld %lld", &x, &y, &z);
		if( x==y ) continue;
		edge[x].push_back((Edge){y, z});
		edge[y].push_back((Edge){x, z});
	}
	
	// 二分 求最小fi
	ll left = minf;
	ll right = maxf;
	if( check(right) ){
		printf("AFK");
		return 0;
	}
	
	while( left<=right ){
		long long mid = (left+right)/2;
		if(check(mid)) { // 还可以继续小
			left = mid + 1;
		}
		else{
			right = mid - 1;
		}
	}

		printf("%lld", left);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值