• 二分总结


    第一部分思考过程

    一般写二分的思考顺序是这样的:首先通过题目背景和check(mid)函数的逻辑,判断答案落在左半区间还是右半区间。
    左右半区间的划分方式一共有两种:

    中点mid属于左半区间,则左半区间是[l, mid],右半区间是[mid+1, r],更新方式是r = mid;或者 l = mid + 1;,此时用第一个模板;
    中点mid属于右半区间,则左半区间是[l, mid-1],右半区间是[mid, r],更新方式是r = mid - 1;或者 l = mid;,此时用第二个模板;

    二分模板

    二分浮点数

    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
        double x;
       cin>>x;
        double l=0;
        double r=x;
        while(r-l>1e-8)
        {
            double mid=(l+r)/2;
            if(mid*mid>=x)
                r=mid;
            else l=mid;
        }
        cout<<l<<endl;
    }
    

    有一个经验之谈就是,题目让你保留几位小数,我们比题目多保留两位小数

    第三部分应用

    二分的基础用法是在单调序列或单调函数中进行查找,因此当问题的答案具有单调性的时候,我们就可以通过二分把求解转化为判定,根据复杂度理论(判定的时间复杂度小于求解)

    如果题目说序列单调不减,那么我们很容易就想到二分;

    最方便的就是使用stl自带的二分函数

    upper_bound和lower_bound

    这两个函数的作用是二分查找一个数在数组中出现的位置,区别是upper_bound返回的是数组中第一个大于搜索数的位置而lower_bound返回的是数组中第一个不小于搜素数的位置

    函数的用法lower_bound(a.begin(),a.end(),x)-a;

    x为要查找的数;
    注意在函数的后面要-a
    也就是减去地址

    在这一题中函数的写法lower_bound(a+1,a+1+n,x);

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e6+10;
    inline int read()
    {
        int x=0;
        char c;
        int f=1;
        x=getchar();
        while(c<'0'||c>'9')
        {
            if(c=='-')
             f=-1;
            c=getchar();
        }
        while(x>='0'&&x<='9')
        {
            x=x*10,x=x+c-'0';
            c=getchar();
        }
        return x*f;
    }
    int a[maxn];
    int main()
    {
     int n=read();
     int m=read();
     for(int i=1;i<=n;i++)
         a[i]=read();
        while(m--)
        {
            int x=read();
            int ans=lower_bound(a+1,a+1+n,x);
            if(x!=a[ans])
                cout<<-1<<" ";
            else cout<<ans<<" ";
        }
        return 0;
    }
    

    分析

    这一天看了看二分,发现不是特别难。

    在学习一个算法的时候我们要明白我们为什么要学习它

    因为二分可以使复杂度降低到(log(n)),在大数据的情况下,比(O(n))朴素的暴力要好。

    什么时候使用

    在题目中说到最大值最小,或者说最小值最大,就可以使用二分进行求解

    要想使用二分搜索法来解这种「最大值最小化」的题目,需要满足以下三个条件:

    1. 答案在一个固定区间内;

    2. eg:

      比如我们进行抄书,那就暗示着我们二分出来的答案所对应的人数要小于等于总人数,否则就是不合法的,即check函数return tot<=m(tot是当前二分出来的答案所对应的人数,m是总人数)

    3. 可能查找一个符合条件的值不是很容易,但是要求能比较容易地判断某个值是否是符合条件的;

    4. 可行解对于区间满足一定的单调性。换言之,如果(x) 是符合条件的,那么有 (x+1)或者 (x-1)也符合条件。(这样下来就满足了上面提到的单调性)

    原理

    二分法把一个寻找极值的问题转化成一个判定的问题(用二分搜索来找这个极值)。类比枚举法,我们

    当时是枚举答案的可能情况,现在由于单调性,我们不再需要一个个枚举,利用二分的思路,就可以用

    更优的方法解决「最大值最小」、「最小值最大」。这种解法也成为是「二分答案」,常见于解题报告

    中。

    心得

    我觉得二分中比较重要的就是check函数

    这个函数帮助我们不断逼近答案,

    check函数中进行模拟

    判断当前$check(int k) $$ k$是否符合

    在主函数中的(while)循环里面的条件,根据题面的不同进行修改,比如有的是浮点数,就需要让(r-l<=0.000001),记得初始化(l=0,r=1e9)(正无穷)

    例题

    题目背景

    大多数人的错误原因:尽可能让前面的人少抄写,如果前几个人可以不写则不写,对应的人输出 0 0

    不过,已经修改数据,保证每个人都有活可干。

    题目描述

    现在要把 m 本有顺序的书分给 k个人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一、第三、第四本书给同一个人抄写。

    现在请你设计一种方案,使得复制时间最短。复制时间为抄写页数最多的人用去的时间。

    输入格式

    第一行两个整数 m,k

    第二行 m 个整数,第 i个整数表示第 i 本书的页数。

    输出格式

    共 k行,每行两个整数,第 i 行表示第 i个人抄写的书的起始编号和终止编号。 k 行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写。

    输入输出样例

    输入

    9 3

    1 2 3 4 5 6 7 8 9

    输出

    1 5

    6 7

    8 9

    这道题典型的二分题,但是它要求尽可能让前面的人少抄写

    这怎么办,我们可以倒着枚举,这样就可以使后面的人抄尽量多的书。

    再注意一下细节,这道题就可以A掉了

    代码

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=600;
    int m,k;
    int a[maxn];
    bool check(int fuzhi)
    {
    	int tot=1;
    	int cnt=0;
    	for(int i=1;i<=m;i++)
    	{
    		if(a[i]>fuzhi)
    		return false;
    		if(tot>k)
    		return false;
    		if(cnt+a[i]<=fuzhi)
    		cnt+=a[i];
    		else cnt=a[i],tot++;
    	}
    	return tot<=k;
    }
    int main(){
    	cin>>m>>k;
    	for(int i=1;i<=m;i++)
    	cin>>a[i];
    	int l=0;
    	int r=1e9;
    	while(l<=r)
    	{
    		int mid=(l+r)/2;
    		if(check(mid))
    		r=mid-1;
    		else l=mid+1;
    	}
    	int tail[maxn];
    	int head[maxn];
    	int cnt=0;
    	int tot=k;
    	tail[tot]=m;
    	head[1]=1;
    	for(int i=m;i>=1;i--)
    	{
    	
    	
    		if(cnt+a[i]>l)
    		{
    			cnt=a[i];
    			head[tot]=i+1;
    			tail[--tot]=i;
    		}
    		else cnt=cnt+a[i];
    	}
    	for(int i=1;i<=k;i++)
    	cout<<head[i]<<" "<<tail[i]<<endl;
    	return 0;
    } 
    
  • 相关阅读:
    RP2833 FPGA对应串口标识
    rp2833 网卡以及串口与接插件位置关系
    环境检测 短信收发的测试
    #A号板测试汇总
    #8号板测试汇总
    #2号板测试汇总
    #6号板问题
    【POJ3045】Cow Acrobats(贪心)
    【HDU1219】AC Me(水题)
    BUPT2017 wintertraining(15) #2 题解
  • 原文地址:https://www.cnblogs.com/bangdexuanyuan/p/13582304.html
Copyright © 2020-2023  润新知