基础算法——二分
Posted rfdragon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基础算法——二分相关的知识,希望对你有一定的参考价值。
上次我们讲了贪心,上题:
题目描述: RFdragon摘了n种果子,每种果子堆成一堆,现在RFdragon打算将所有果子运回家,因此决定将所有果子合并成一堆。每次RFdragon可以选择任意两堆果子进行合并,消耗等同于两堆果子质量之和的力气。请你求出一个合并方案,使得RFdragon消耗的力气最少。 输入描述: 第一行一个正整数n。 第二行n个正整数,表示每堆果子的质量。 输出描述: 一个正整数,表示消耗力气的最小值。 输入样例: 5 3 2 5 2 1 输出样例: 29 其他说明: n<100000,所有数据均在int范围内。
今天我们要讲的是二分。设想这样一个场景:让你从1~100之间猜一个数,每次告诉你大了或者小了,要求尝试次数最少,你会猜几?想都不用想,肯定先猜50。这个时候,你已经在用二分的思想来解决问题了。二分主要分两种:二分查找和二分答案。我们先来看二分查找。我们直接来看一道题:
题目描述: 输入n个数,将n个数从小到大排序,问k排在第几。 输入描述: 第一行两个正整数n和k,中间用一个空格隔开。 第二行有n个正整数,中间有一个空格隔开。 输出描述: 一个正整数,表示k排在第几位。 输入样例: 5 7 7 3 5 6 9 输出样例: 4 其他说明: n<1000000,所有输入数据均在int范围内,保证没有重复的数。
它要怎么用二分来实现呢?首先,不用说,肯定要将输入的所有书进行排序。那之后呢?我们还回到刚才猜数的例子。假设你猜了50,告诉你50小了,你就排除了1~50之间的所有数,范围变为51~100;你再猜75,假如75也小了,那你又排除了51~75之间的所有数,范围变为76~100。因此,我们需要两个数来记录数据的范围。我一般喜欢用head和tail进行表示,如下:
int head=1,tail=n;
head表示最小值,tail表示最大值。为什么tail要等于n呢?这是因为,n是数组中最大的数的下标,这个数再大,下标也不可能超过n。每次取一个中间值,表示我们“猜”的那个数。
mid=(head+tail)/2; if(a[mid]>k) tail=mid-1; else if(a[mid]<k) head=mid+1; else { printf("%d",mid); return 0; }
这就是我们“猜”数并缩小范围的过程。掌握了核心代码,题就不难做了。完整代码如下:
#include<cstdio> int n,k,a[1000000],head=1,tail,mid; int main() { scanf("%d %d",&n,&k); tail=n; for(int i=1;i<=n;i++) scanf("%d",&a[i]); while(head<=tail) { mid=(head+tail)/2; if(a[mid]==k) { printf("%d",mid); return 0; } else if(a[mid]>k) tail=mid-1; else head=mid+1; } return 0; }
这就是二分查找。那么二分答案又是什么呢?有一些题中,答案很难被直接确定,需要先确定范围,然后在范围中逐渐尝试。用一般的查找方法自然很慢,但用二分来查找答案就快的多了。为了让大家更好理解二分答案,先上一道题:
题目描述: RFdragon家里有一些废品,由于废品实在太多了,因此需要分批运走。黑心的收废品的叔叔为了盈利,不但不给钱,反而还向RFdragon索要工钱。RFdragon好心的同意了。 RFdragon会将所有的废品分成n批运走,运走每批废品都要花一定量的钱。由于叔叔很黑心,因此向RFdragon提出以下要求:RFdragon每天给叔叔k元钱,k是一个定值,叔叔会运走几批废品,但是这几批废品必须是连续的,并且每次必须将一整批全部运走;如果运走几批废品后钱有剩余,但不够运走下一批废品,那么剩下的钱会被叔叔收入腰包。由于忍受不了废品散发的味道,RFdragon要求运走所有废品的天数不能超过m天,并且k越小越好。请你帮他求出k的最小值。 输入描述: 第一行三个正整数,中间用空格隔开,分别表示n、m。 第二行有n个正整数,表示运走每批废品的价格。 输出描述: 一个正整数,表示k的最小值。 其他说明: 所有数据<10000。
首先,我们要确定二分范围。最小值一定是所有废品中花钱最多的那个,最大值一定是所有废品所花钱数之和。问题来了,我们每次确定一个mid值之后,如何判断这个mid值是否可以满足题意呢?我们不妨写一个check函数。
bool check(int x)//参数x表示假设k=x { int cur=x,sum=1;//cur表示当前剩余钱数,sum表示运走所有废品所需天数 for(int i=1;i<=n;i++) { if(cur>a[i]) cur-=a[i];//假设剩余钱数能运走当前这批废品,就让叔叔运走他 else { cur=x-a[i]; sum++;//否则就让叔叔(使用)明天(的钱)运走他 } } return sum<=m; }
完整代码如下:
#include<algorithm> #include<cstdio> using namespace std; int n,m,a[10000]; bool check(int x) { int cur=x,sum=1; for(int i=1;i<=n;i++) { if(cur>a[i]) cur-=a[i]; else { cur=x-a[i]; sum++; } } return sum<=m; } int main() { int head=0,tail=0,mid; scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); tail+=a[i]; head=max(head,a[i]); } while(head<tail) { mid=(head+tail)/2; if(check(mid)) tail=mid; else head=mid+1; } printf("%d",head); return 0; }
这就是二分的用法,你学会了吗?
//答案代码 #include<algorithm> #include<cstdio> using namespace std; int n,a[10000],ans; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+1+n); for(int i=1;i<=n;i++) { a[i]+=a[i-1]; ans+=a[i]; } printf("%d",ans); return 0; }
Created by RFdragon
以上是关于基础算法——二分的主要内容,如果未能解决你的问题,请参考以下文章
Java八股文面试题 基础篇 -- 二分查找算法冒泡排序选择排序插入排序希尔排序快速排序