1. 程式人生 > 實用技巧 >常用技巧:二分搜尋

常用技巧:二分搜尋

二分不僅是簡單的一個查詢工具,而且是一種型別題目的解體思路。困在了一道題上:求最大值的最小(大概是這種型別......)遇到這種情況應該考慮二分查詢。先將可能的最大值列出來,對該數列排序,在該數列上進行二分,判斷當前節點是否合適,由此求出最大值的最小值。

例題洛谷P1182、P1642,這裡以P1642為例(二分+最短路)

#include<bits/stdc++.h>
using namespace std;
int n,m,b;
int fee[10005],cfee[10005];
struct edge
{
    int next,cost;
};
vector<edge> edges[10005
]; bool mark[10005]; struct node { int index; long long dis; bool operator <(const node &x) const { return x.dis<dis; } }; long long dis[10005]; priority_queue<node> q; bool check(int x) { if(fee[1]>x||fee[n]>x){ return false; } while
(!q.empty()){ q.pop(); } for(int i=1;i<=n;i++){ if(fee[i]>x){ mark[i]=true; } else{ mark[i]=false; } dis[i]=0x7fffffffffffffff; } dis[1]=0; q.push((node){1,0}); while(!q.empty()){ node now=q.top(); q.pop();
if(mark[now.index]){ continue; } mark[now.index]=true; for(int i=0;i<edges[now.index].size();i++){ int next=edges[now.index][i].next; if(mark[next]){ continue; } int cost=edges[now.index][i].cost; if(dis[next]>now.dis+cost){ dis[next]=now.dis+cost; q.push((node){next,dis[next]}); } } } if(dis[n]<=b){ return true; } else{ return false; } } int main() { scanf("%d%d%d",&n,&m,&b); for(int i=1;i<=n;i++){ scanf("%d",&fee[i]); cfee[i]=fee[i]; edges[i].clear(); } while(m--){ int a,b,c; scanf("%d%d%d",&a,&b,&c); if(a==b){ continue; } edge temp; temp.next=b; temp.cost=c; edges[a].push_back(temp); temp.next=a; edges[b].push_back(temp); } sort(cfee+1,cfee+n+1); if(!check(cfee[n])){ printf("AFK\n"); return 0; } int l=1,r=n,ans=cfee[n]; while(l<=r){ int mid=(l+r)/2; if(check(cfee[mid])){ r=mid-1; ans=cfee[mid]; } else{ l=mid+1; } } printf("%d\n",ans); return 0; }

這型別題應該立馬想到二分。難點主要在於怎麼構建出二分數列以及怎麼判斷。

對於浮點數的二分查詢,需要注意精度的問題。通常兩種方法,第一種是為迴圈設定結束的最低精度,第二種是設定最大迴圈數(後者比較保險)。POJ1064,很多細節,需要時常看一下

//POJ1064
#include<stdio.h>
#include<algorithm>
#include<cmath>
using namespace std;
#define eps 1e-10
int n,m;
double len[10005];
bool judge(double mid){
    int ans=0;
    for(int i=1;i<=n;i++){
        ans+=(int)(len[i]/mid);
    }
    return ans>=m;
}
int main(){
    scanf("%d%d",&n,&m);
    double l=0,r=0.0,ans;
    bool flag=false;
    for(int i=1;i<=n;i++){
        scanf("%lf",&len[i]);
        r=max(r,len[i]);
    }
    for(int i=1;i<=100;i++){
        double mid=(l+r)/2;
        if(judge(mid)){
            l=mid;
            flag=true;
            ans=mid;
        }
        else{
            r=mid;
        }
    }
    if(flag) printf("%.2f\n",floor(ans*100)/100);
    else printf("0.00\n");
    return 0;
}

除了上面提到的最大化最小值問題,類似的還有最大化平均值。【有n個物品的重量和價值分別為wi和vi,從中選出k個物品使得單位重量的價值最大】不是簡單的貪心,在這裡是要求總價值/總重量。這種情況可以二分,判斷當前的單位重量價值有沒有可能用前k個物品來實現。POJ3111

#include<stdio.h>
#include<algorithm>
using namespace std;
int n,k,ans[100005];
struct item{
    int loc;
    double value,weight,tmp;
    bool operator <(const item &A) const{
        return tmp>A.tmp;
    }
}items[100005];
bool judge(double x){
    for(int i=1;i<=n;i++){
        items[i].tmp=items[i].value-x*items[i].weight;
    }
    sort(items+1,items+n+1);
    double sum=0;
    for(int i=1;i<=k;i++){
        sum+=items[i].tmp;
        ans[i]=items[i].loc;
    }
    return sum>=0;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%lf%lf",&items[i].value,&items[i].weight);
        items[i].loc=i;
    }
    double l=0,r=10000005;
    for(int i=1;i<=100;i++){
        double mid=(l+r)/2;
        if(judge(mid)) l=mid;
        else r=mid;
    }
    for(int i=1;i<=k;i++){
        if(i!=1) printf(" ");
        printf("%d",ans[i]);
    }
    return 0;
}

推薦題目:求第m個大的數,POJ3685。比較好想,程式碼不貼了

POJ2010,因為最後結果只與中位數成績有關與其他成績無關,那麼對每個成績進行記錄比他小的成績裡最小的(n-1)/2個f的和,以及比他大的成績裡最小的(n-1)/2個f的和。最後二分就行

#include<stdio.h>
#include<algorithm>
#include<queue>
using namespace std;
int n,c,f,h,left[100005],right[100005],ans;
struct cow{
    int s,f;
    bool operator < (const cow &x) const{
        return s<x.s;
    }
}cows[100005];
priority_queue<int> que;

int main(){
    scanf("%d%d%d",&n,&c,&f);
    for(int i=1;i<=c;i++){
        scanf("%d%d",&cows[i].s,&cows[i].f);
    }
    sort(cows+1,cows+c+1);
    h=(n-1)/2;

    int sum=0;
    while(!que.empty()) que.pop();
    for(int i=1;i<=c;i++){
        left[i]=sum;
        if(i<=h){
            sum+=cows[i].f;
            que.push(cows[i].f);
        }
        else{
            if(cows[i].f<que.top()){
                sum-=que.top();
                que.pop();
                que.push(cows[i].f);
                sum+=cows[i].f;
            }
        }
    }

    sum=0;
    while(!que.empty()) que.pop();
    for(int i=c;i>=1;i--){
        right[i]=sum;
        if(c-i+1<=h){
            sum+=cows[i].f;
            que.push(cows[i].f);
        }
        else{
            if(cows[i].f<que.top()){
                sum-=que.top();
                que.pop();
                que.push(cows[i].f);
                sum+=cows[i].f;
            }
        }
    }

    bool flag=false;
    for(int i=c-h;i>=h+1;i--){
        if(left[i]+cows[i].f+right[i]<=f){
            flag=true;
            printf("%d\n",cows[i].s);
            return 0;
        }
    }
    if(!flag) printf("-1\n");
}