二分法

QLU_ACM 淺談二分搜索技術(shù)

by StilllFantasy

二分思想為何物浴捆?

二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是泡态,折半查找要求線性表必須采用順序存儲(chǔ)結(jié)構(gòu),而且表中元素按關(guān)鍵字有序排列迂卢。

關(guān)鍵點(diǎn):

  • 優(yōu)點(diǎn):每次折半某弦,速度較快
  • 缺點(diǎn):待查表必須為順序表-->二分搜索的限制
  • 復(fù)雜度:O ( log N )

什么意思桐汤?

簡而言之就是,待查表必須是有序的靶壮,無序的話必須先排序怔毛,比如在數(shù)組二分搜索某個(gè)數(shù)的時(shí)候

舉個(gè)栗子:

    int a[10] = {1,2,3,4,5,6,7,8,9};
int search(int K)
{
    for (int i = 0; i < N; i++)
        if (num[i] == K)
        {
            return i;
        }
    return -1;
}
int search(int K, int L, int R)
{
    int l = L;
    int r = R;
    while (l <= r)
    {
        if(l == r && num[l] != K)
            return -1;

        int mid = (l + r) / 2;

        if (num[mid] < K)
            return search(K, mid + 1, R);

        else if (num[mid] > K)
            return search(K, L, mid - 1);

        else
            return mid;
    }
    return -1;
}

二分不難寫吧?

  • 其實(shí)二分的思想還是蠻容易理解的
  • 重點(diǎn)在于:它除了能在數(shù)組里查數(shù)用腾降,還能干啥呢拣度?

二分答案!

  • 特點(diǎn):速度快螃壤、神奇

注意:

  • 答案滿足單調(diào)性
  • 預(yù)估答案區(qū)間 minAns ~ maxAns

步驟:

找區(qū)間-->取中間判斷是不是答案-->折半直到找到答案

關(guān)于此類題目的一般關(guān)鍵詞:

  • 最大值盡量小
  • 最小值盡量大
  • (在某種情況下)最小值是多少
  • (在某種情況下)最大值是多少

說到這里抗果,新手可能有點(diǎn)蒙,沒關(guān)系奸晴,我們來看幾個(gè)例題:

It is the time to 舉栗子冤馏!

例題Z:買糖

小朋有個(gè)弟弟,小凱寄啼。小凱喜歡哭鼻子逮光,經(jīng)常因?yàn)闆]糖吃而哭鼻子。小朋要哄著小凱辕录,給小凱買糖吃睦霎,小朋有 N 元錢,商店老板小康賣的糖 K 元一個(gè)走诞,共有 M 個(gè)副女,小凱多吃一塊糖就少哭一次,但事實(shí)也不是任由小凱的蚣旱,他吃糖就會(huì)把糖紙亂扔碑幅,弄得校園很臟,校園里臟的程度y與小凱吃糖個(gè)數(shù)x關(guān)系是:y=x^2-20181314*x,當(dāng)校園里臟的程度超過 S 時(shí)塞绿,小凱的姐姐小林就會(huì)打她沟涨,小凱雖想吃糖但他不想挨打啊,問小凱在不被挨打的情況下异吻,最多可以少哭多少次裹赴?
數(shù)據(jù)范圍 1<=K,N,M,S<=10^9

  • (這題不要當(dāng)真,瞎搞用的诀浪,測試一波O(N)與O(logN)的差距)
#include <iostream>
#include <cmath>
using namespace std;
long long n,m,k,s;
int ok=1;
bool isok(int z)
{
    if(z<=m&&z*k<=n&&2*z<=s)
    {
        return 1;
    }
    else return 0;
}
int main()
{
    cin>>n>>m>>k>>s;

    ///樸素查找
    for(int i=1;i<=m;i++)
    if(!isok(i))
    {
        cout<<"ans is "<<i<<endl;
        ok=0;
        break;
    }
    if(ok)
    cout<<"ans is "<<m<<endl;
   
    ///二分查找
    long long l=1,r=m;
    long long ans;
    while(l<=r)
    {
        long long mid=(l+r)/2;
        if(isok(mid))
        {
            l=mid+1;
            ans=mid;
        }
        else r=mid-1;
    }
    cout<<"ans is "<<ans;  
    return 0;
}

例題A:安排牛棚 ->鏈接

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int x[100005];
int c, n;
int R, L = 1e9 + 9;
int s;
int ans;
bool ansok(int key)
{
    int now = 1; //now表示上一個(gè)放牛的牛棚編號棋返,因?yàn)榈谝淮卧谝惶柵E锓畔拢詎ow=1
    int sum = 1; //sum表示放的牛的個(gè)數(shù)雷猪,初始時(shí)我們肯定把第一個(gè)牛棚放上牛睛竣,所以sum=1
    for (int i = 2; i <= n; i++)
    {
        if (x[i] - x[now] >= key)
        {
            sum++;
            now = i;
        }
    }
    if (sum >= c)
        return 1;
    else
        return 0;
}
int main()
{
    cin >> n >> c;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &x[i]);
        s = max(s, x[i]);
        L = min(L, x[i]);
    }
    sort(x + 1, x + 1 + n);
    R = s / c + 1;
    while (L <= R)
    {
        int mid = (L + R) / 2;
        if (ansok(mid))
        {
            L = mid + 1;
            ans = mid;
        }
        else
            R = mid - 1;
    }
    cout << ans;
    return 0;
}

/*
    ansok(int key) :
    我們假設(shè)key就是最小值,那么我們就嘗試按照這樣的最小值能不能把牛給安排下
    如果兩個(gè)牛棚之間的距離小于key求摇,那么這個(gè)牛棚不能放牛射沟,因?yàn)槲覀円呀?jīng)假設(shè)了key是最小值
    這個(gè)選擇的過程不理解的話模擬一下就可以
*/

例題B:跳石頭 ->鏈接

#include <iostream>
#include <algorithm>
using namespace std;
int N, M, L;
int minAns = 1e9 + 9;
int maxAns;
int ans;
int dis[50006];
bool ansok(int key)
{
    int sum = 0;
    int now = 0;
    for (int i = 1; i <= N + 1; i++)
    {
        if (dis[i] - dis[now] < key)
            sum++;
        else
            now = i;
    }
    if (sum <= M)
        return 1;
    else
        return 0;
}
int main()
{
    cin >> L >> N >> M;
    for (int i = 1; i <= N; i++)
    {
        cin >> dis[i];
    }
    dis[N + 1] = L;     //虛擬一個(gè)終點(diǎn) N+1
    int left = 0;
    int right = L;
    while (left <= right)
    {
        int mid = (left + right) / 2;
        if (ansok(mid))
        {
            ans = mid;
            left = mid + 1;
        }
        else
        {
            right = mid - 1;
        }
    }
    cout << ans;
    return 0;
}
/*
    這題有一個(gè)坑在于起點(diǎn)到終點(diǎn)的距離要大于第 N 個(gè)點(diǎn)到起點(diǎn)的距離
    我們虛擬一個(gè) N+1 點(diǎn)殊者,來判斷第 N 個(gè)石頭是不是要拿走 
*/

例題C:組裝玩具 ->鏈接

  • 這題可以用二分解決,但不像是之前兩個(gè)題那么“模板化” 验夯,需要注意的地方要單獨(dú)處理猖吴,本題深入溝通可以私聊我QQ
#include <iostream>
#include <algorithm>
using namespace std;
struct toy
{
    int a;
    int b;
    int c;
} s[1000006];
int n, m;
int cnt;
int ans = 0;
int sum;
int k[1000006];
bool cmp(toy x, toy y)
{
    return x.c < y.c;
}
int ifok(int k)     //判斷二分出的答案是否可行
{
    int mm = 0;
    for (int i = 0; i < n; i++)
    {
        if (s[i].c >= k)
            return m - mm;
        mm += k * s[i].b - s[i].a;
        if (m - mm < 0)
            return m - mm;
    }
    return m - mm;
}
int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++)
        cin >> s[i].a;

    for (int i = 0; i < n; i++)
    {
        cin >> s[i].b;
        s[i].c = s[i].a / s[i].b;
        sum += s[i].b;
    }
    sort(s, s + n, cmp);
    for (int i = 0; i < n; i++)
    {
        if (k[cnt] != s[i].c)
            k[++cnt] = s[i].c;
    }
    int mm = ifok(k[cnt] + 1);
    if (mm == 0)
        cout << cnt + 1;
    else if (mm > 0)
        cout << mm / sum + k[cnt] + 1;
    else    //本題核心:二分答案
    {
        int l = 0;
        int r = k[cnt];
        while (l <= r)
        {
            int mid = (l + r) / 2;
            if (ifok(mid) >= 0)
            {
                ans = mid;
                l = mid + 1;
            }
            else
            {
                r = mid - 1;
            }
        }
        cout << ans;  
    }
    return 0;
}
 cout << "Hello, QLU_ACM_club !!!" << endl;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市簿姨,隨后出現(xiàn)的幾起案子距误,更是在濱河造成了極大的恐慌,老刑警劉巖扁位,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件准潭,死亡現(xiàn)場離奇詭異,居然都是意外死亡域仇,警方通過查閱死者的電腦和手機(jī)刑然,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來暇务,“玉大人泼掠,你說我怎么就攤上這事】严福” “怎么了择镇?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長括改。 經(jīng)常有香客問我腻豌,道長,這世上最難降的妖魔是什么嘱能? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任吝梅,我火速辦了婚禮,結(jié)果婚禮上惹骂,老公的妹妹穿的比我還像新娘苏携。我一直安慰自己,他們只是感情好对粪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布右冻。 她就那樣靜靜地躺著,像睡著了一般著拭。 火紅的嫁衣襯著肌膚如雪纱扭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天茫死,我揣著相機(jī)與錄音跪但,去河邊找鬼履羞。 笑死峦萎,一個(gè)胖子當(dāng)著我的面吹牛屡久,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播爱榔,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼被环,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了详幽?” 一聲冷哼從身側(cè)響起筛欢,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唇聘,沒想到半個(gè)月后版姑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡迟郎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年剥险,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宪肖。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡表制,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出控乾,到底是詐尸還是另有隱情么介,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布蜕衡,位于F島的核電站壤短,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏衷咽。R本人自食惡果不足惜鸽扁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望镶骗。 院中可真熱鬧桶现,春花似錦、人聲如沸鼎姊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽相寇。三九已至慰于,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唤衫,已是汗流浹背婆赠。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佳励,地道東北人休里。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓蛆挫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親妙黍。 傳聞我的和親對象是個(gè)殘疾皇子悴侵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345