基礎算法——二分
阿新 • • 發佈:2019-05-11
解決問題 嘗試 span 收入 color 二分答案 分答 題目 做了
上次我們講了貪心,上題:
題目描述: 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
基礎算法——二分