常用技巧:二分搜索

二分不仅是简单的一个查找工具,而且是一种类型题目的解体思路。困在了一道题上:求最大值的最小(大概是这种类型......)遇到这种情况应该考虑二分查找。先将可能的最大值列出来,对该数列排序,在该数列上进行二分,判断当前节点是否合适,由此求出最大值的最小值。

例题洛谷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");
}

 

posted @ 2020-10-09 14:57  太山多桢  阅读(127)  评论(0编辑  收藏  举报