zoukankan      html  css  js  c++  java
  • 二分之一网打尽

    分治算法
      所谓分治就是指分而治之,即将较大规模的问题分解成几个较小规模的问题,通过对较小规模问题的求解达到对整个问题的求解。当我们将问题分解成两个较小问题求解时的分治方法称之为二分。
      你们玩过猜数字的游戏吗?你的朋友心里想一个 1000 以内的正整数,你可以给出一个数字 x ,你朋友只要回答“比 x 大”或者“比 x 小”或者“猜中”,你能保证在 10 次以内猜中吗?运气好只要一次就猜中。

      开始猜测是 1 到 1000 之间,你可以先猜 500,运气好可以一次猜中,如果答案比500大,显然答案不可能在1到500之间,下一次猜测的区间变为501到1000,如果答案比500小,那答案不可能在500到1000之间,下一次猜测的区间变为1到499.只要每次都猜测区间的中间点,这样就可以把猜测区间缩小一半。由于 1000 / 210 < 1,因此不超过 10 次询问区间就可以缩小为1,答案就会猜中了,这就是二分的基本思想。

      每一次使得可选的范围缩小一半,最终使得范围缩小为一个数,从而得出答案。假设问的范围是 1 到 n ,根据 n / 2x <= 1得 x >= logn,所以我们只需要问O(logn)次就能知道答案了。

      需要注意的是使用二分法有一个重要的前提,就是有序性,下面通过几个例子来体会二分法的应用。
    1、给定一个长度为 n 的单调递增的序列,即序列中每一个数都比前一个数大。有 m 次询问,每次询问一个 x ,问序列中最后一个小于等于 x 的数是什么?

    输入:第一行两个整数n, m.

    接下来一行 n 个数,表示这个序列。

    接下来m行每行一个数,表示一个询问。

    输出:

    输出共 m 行,表示序列中最后一个小于等于 x 的数是什么。假如没有,则输出 -1.

    样例输入:

    5 3

    1 2 3 4 6

    5

    1

    3

    样例输出:

    4

    1

    3

    代码实现1:

    #include <iostream>
    using namespace std;
    const int N = 1001;
    int n, m, a[N];
    
    int divide(int x)
    {
        int l = 0, r = n - 1;
        while(l <= r)
        {
            int mid = l + r >> 1;
            if(a[mid] <= x) l = mid + 1;
            else r = mid - 1;
        }
        
        return a[r];//这里不能用a[l],a[l]是第一个大于x数
    }
    
    int main()
    {
        cin >> n >> m;
        for(int i = 0; i < n; i ++) cin >> a[i];
        
        while(m --)
        {
            int x;
            cin >> x;
            cout << divide(x) << endl;
        }
    }

    具体的缩小过程:

    5 3
    1 2 3 4 6
    5
    l: 4 mid: 3 r : 6
    l: 6 mid: 4 r : 6
    l: 6 mid: 6 r : 4
    4
    1
    l: 1 mid: 3 r : 2
    l: 2 mid: 1 r : 2
    l: 2 mid: 2 r : 1
    1
    3
    l: 4 mid: 3 r : 6
    l: 4 mid: 4 r : 3
    3

    Process returned 0 (0x0) execution time : 1.875 s
    Press any key to continue.

    二分这里下面这种也可以:

    while(l < r)
        {
            int mid = l + r + 1 >> 1;
            if(a[mid] <= x) l = mid;
            else r = mid - 1;
        }
        
        return a[l]; //a[l]和a[r]都可以
    

      这个调试过程如下:

    5 3
    1 2 3 4 6
    5
    l: 3 mid: 3 r : 6
    l: 4 mid: 4 r : 6
    l: 4 mid: 6 r : 4
    4
    1
    l: 1 mid: 3 r : 2
    l: 1 mid: 2 r : 1
    1
    3
    l: 3 mid: 3 r : 6
    l: 3 mid: 4 r : 3
    3

    Process returned 0 (0x0) execution time : 2.031 s
    Press any key to continue.

     还可以直接用STL中upper_bound(大于)、lower_bound(大于等于),从小到大排好序的数组,得到第一个大于x的数,第一个大于等于x的数。

    int pos = upper_bound(a, a + n, x) - a;

    从大到小排好序的数组里面,upper_bound、lower_bound返回第一个小于等于 x 的数组,第一个小于 x 的数组。

    int pos = upper_bound(a, a + n, x, greater<int>()) - a;

    代码如下:

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int N = 1001;
    int n, m, a[N];
    
    int main()
    {
        cin >> n >> m;
        for(int i = 0; i < n; i ++) cin >> a[i];
        reverse(a, a + n);
    
        while(m --)
        {
            int x;
            cin >> x;
            int ans = lower_bound(a, a + n, x, greater<int>()) - a;
    
            cout << a[ans] << endl;
        }
    }

    5 3
    1 2 3 4 6
    5
    4
    1
    1
    3
    3

    Process returned 0 (0x0) execution time : 2.312 s
    Press any key to continue.

  • 相关阅读:
    记录一些经常被忽略的结论
    Eclipse 各种问题解决记录
    Feign 动态URL 解决记录
    Nacos 启动失败
    多git账号配置解决方案
    记一次java.lang.StackOverflowError
    StringBuilder 以及 StringBuffer默认大小与扩容
    MySQL索引背后的数据结构及原理
    我没有高并发项目经验,但是面试的时候经常被问到高并发、性能调优方面的问题,有什么办法可以解决吗?
    istio 学习之 手动注入sidecar
  • 原文地址:https://www.cnblogs.com/longxue1991/p/13049521.html
Copyright © 2011-2022 走看看