1. 程式人生 > >基礎算法——二分

基礎算法——二分

解決問題 嘗試 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

基礎算法——二分