zoukankan      html  css  js  c++  java
  • [Algorithm] 二分查找之旅

    我还记得我当年Openjudge上的二分查找整一章都没做QwQ

    果然现在要还债了

    现在开始补二分查找

    一、基本模型

    二分查找适用范围为一段连续的、具有单调性的区间(所以通常把二分目标所在范围意淫成数轴区间),

    这样最优解的左右两边必有一边为可行解(这样才能缩小区间找到最优解)。

    所以二分前还要保证二分区间有序。

    下面是二分查找基本模板

    int check(int); //用于判定区间中点是否为可行解
    
    int l = 1,r = L; //初始区间左右端点
    
    while(l+1 < r){
        int mid = (l+r)/2; 
        if(check(mid) l = mid; //其实这里的区间缩减方式需要具体情况具体修改
        else r = mid-1;
    }

    显然,缩减区间部分不是重点,最关键的是那个check函数(是不是想到了函数式编程?)

    区间缩减循环时间复杂度应该是O(logN),已经被核心思想固定下来了,因此关键在check

    check用于确定当前点是否为可行解,一般返回逻辑值(不过我个人倾向只返回计算值然后在while里判断)

    有几个坑点需要注意的:

    1. 二分终止条件和之后的最终确定最优解需要好好想想。

    PS: l < r 搭配l = mid肯定出错,因为当l r在区间上相差1时l 恒等于 mid

    2. 在缩减区间的过程中务必做到完善的分类讨论

    PS:我经常见到有的人是搭配l = mid+1和r = mid-1加上l <= r,这样我不知道怎么考虑最优解为mid的情况,所以我是不敢这么玩的QwQ

    二、题目

    1. 河中跳房子  NOIP2015

    二分目标区间为1 ~ L,目标为最小距离的最大值(也就是最优解)。这里check返回的是使x为最小距离的需要搬走的石头数

    这个check有点妙

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<cstring>
     4 #include<algorithm>
     5 #define maxn 100000
     6 using namespace std;
     7 
     8 int L,n,m,arr[maxn];
     9 
    10 int check(int line){
    11     
    12     int pre = 0,ans = 0;
    13     
    14     for(int i = 1;i <= n;i++){ //关键点1
    15         if(arr[i]-pre < line) ans++;
    16         else pre = arr[i];
    17     }
    18     
    19     return ans;
    20 }
    21 
    22 int main(){
    23     scanf("%d%d%d",&L,&n,&m);
    24     
    25     for(int i = 1;i <= n;i++) scanf("%d",&arr[i]);
    26     
    27     arr[++n] = L;
    28     
    29     sort(arr+1,arr+1+n);
    30     
    31     int l = 0,r = L;
    32     
    33     while(l+1 < r){ //关键点2,联系上面的坑点1.
    34         int mid = (l+r)/2;
    35         if(check(mid) <= m) l = mid;
    36         else r = mid-1;
    37     }
    38     
    39     if(check(r) <= m) printf("%d",r);
    40     else printf("%d",l);
    41     
    42     return 0;
    43 }
    View Code

     

    2. 借教室 NOIP2012

     二分区间为申请名单,目标为分配出现问题的那个申请者。那么怎么Check呢?每Check一个人的时候就直接从头计算一遍。虽然这听上去非常的低效但是这只是O((n+m)logn)。再加上差分数组进行优化,最后还是跑过了(可能这是正解?).至于用线段树怎么写我还无头绪。

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<cstring>
     4 #define maxn 1000000
     5 #define mid (l+r)/2
     6 using namespace std;
     7 
     8 int n,m,a,b,c,arr[maxn],data[maxn][3],brr[maxn],x;
     9 
    10 int check(int k){ //O(m+n)
    11     memset(brr,0,sizeof(brr));
    12     
    13     for(int i = 1;i <= k;i++){
    14         brr[data[i][1]] -= data[i][0];
    15         brr[data[i][2]+1] += data[i][0];
    16     }
    17     
    18     x = 0;
    19     
    20     for(int i = 1;i <= n;i++){
    21         x += arr[i]+brr[i];
    22 //        if(k == 2) printf("%d ",x); 
    23         if(x < 0) return 0;
    24     }
    25     
    26 //    cout << endl;
    27     
    28     return 1;
    29 }
    30 
    31 int main(){
    32     
    33     scanf("%d%d",&n,&m);
    34     
    35     for(int i = 1;i <= n;i++){
    36         scanf("%d",&brr[i]);
    37         arr[i] = brr[i] - brr[i-1];
    38     }
    39     
    40 //    for(int i = 1;i <= n;i++){
    41 //        printf("%d ",arr[i]);
    42 //    }
    43     
    44 //    cout << endl;
    45     
    46     
    47     for(int i = 1;i <= m;i++){
    48         scanf("%d%d%d",&data[i][0],&data[i][1],&data[i][2]);
    49     }
    50     
    51     int l = 1,r = m;
    52     while(l < r){
    53         if(check(mid)) l = mid+1;
    54         else r = mid;
    55     }
    56     
    57     if(!check(r)) printf("-1
    %d",r);
    58     else if(!check(l)) printf("-1
    %d",l);
    59     else printf("0");
    60 //    printf("B%d ",r);
    61 //    printf("C%d ",check(2));
    62     return 0;
    63 } 
    推荐不看

    这道题有专门写解题报告。I'm Portal!

    3. 聪明的质监员 NOIP2011

    二分目标为参数W,二分范围为矿石的质量,嗯嗯。显然的参数W影响参与计算的矿石数量,也就线性影响计算出来的检验值,因此可以二分。

    思路倒是简单,但是还是被各种细节坑。QwQ

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<cstring>
     4 #include<cmath>
     5 #define LL long long
     6 #define INF (1LL<<62)
     7 #define maxn 1000000
     8 #define mid (L+R)/2
     9 using namespace std;
    10 
    11 LL n,m,S,ret = INF,maxw = 0,tmp;
    12 LL cnt1[maxn],cnt2[maxn],w[maxn],v[maxn],ql[maxn],qr[maxn];
    13 
    14 int check(int x){
    15     cnt1[0] = cnt2[0] = 0;
    16     for(int i = 1;i <= m;i++){
    17         cnt1[i] = cnt1[i-1];
    18         cnt2[i] = cnt2[i-1];
    19         if(w[i] >= x){
    20             cnt1[i]++;
    21             cnt2[i] += v[i];
    22         }
    23     }
    24     
    25     LL ans = 0;
    26     
    27     for(int i = 1;i <= m;i++){
    28         ans += (cnt1[qr[i]]-cnt1[ql[i]-1])*(cnt2[qr[i]]-cnt2[ql[i]-1]);
    29     }
    30     
    31     return ans;
    32 }
    33 
    34 int main(){
    35     scanf("%lld%lld%lld",&n,&m,&S);
    36     
    37     for(int i = 1;i <= n;i++){
    38         scanf("%lld%lld",&w[i],&v[i]);
    39         maxw = maxw>w[i]?maxw:w[i];
    40     }
    41     
    42     for(int i = 1;i <= m;i++){
    43         scanf("%lld%lld",&ql[i],&qr[i]);
    44     }
    45     
    46     int L = 0,R = maxw+1;
    47     
    48     while(L <= R){
    49         tmp = check(mid);
    50         ret = abs(S-tmp)<ret?abs(S-tmp):ret;
    51         if(tmp >= S) L = mid+1;
    52         else R = mid-1;
    53     }
    54     
    55     printf("%lld",ret);
    56     
    57     return 0;
    58 }
    59 
    60 推荐不看
    推荐不看

    这道题有专门写解题报告。I'm Portal!

    转载请注明出处 -- 如有意见欢迎评论
  • 相关阅读:
    java 接口的定义即实现
    进程的pv。。
    变量,常量,字符,字符串,数组的声明及初始化
    运算符重载
    数组大小的声明 1到100的素数
    第十二周助教总结(2021.4.192021.4.25)
    第十周助教总结(2021.4.52021.4.11)
    第十一周助教总结(2021.4.122021.4.18)
    助教周报(第一轮)王冰炜
    ThinkPHP3.1.2整合UCenter详解(一)
  • 原文地址:https://www.cnblogs.com/Chorolop/p/7205512.html
Copyright © 2011-2022 走看看