zoukankan      html  css  js  c++  java
  • 分治

    分治

        所谓分治就是指的分而治之,即将较大规模的问题分解成几个较小规模的问题,通过对较小规模问题的求解达到对整个问题的求解。当我们将问题分解成两个较小问题求解时的分治方法称之为二分法。

        你们玩过猜数字的游戏吗?你的朋友心里想一个1000以内的正整数,你可以给出一个数字x,你朋友只要回答“比x大”或者“比x小”或者“猜中”,你能保证在10次以内猜中吗?运气好只要一次就猜中。

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

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

    需要注意的是使用二分法有一个重要的前提,就是有序性,下面通过几个例子来体会二分法的应用。

    找数这个简单吧,二分来一波

     

    找数问题

    【题目描述】:

    给一个长度为n的单调递增的正整数序列,即序列中每一个数都比前一个数大。有m个询问,每次询问一个x,问序列中最后一个小于等于x的数是什么?

    输入:

    第一行两个整数n,m

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

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

    输出:

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

    【算法分析】:
    我们用Left表示询问区间的左边界,用Right表示询问区间的右边界,[Left,Right]组成询问区间。一开始Left=1Right=n,我们可以把原始序列的左边想象成若干个无穷小的数,把序列的右边想象成无穷大的数,这样比较好理解。序列已经按照升序排好,保证了二分的有序性。
    每一次二分,我们这样来做:
        ①取区间中间值Mid=(Left+Right)/2
        ②判断Midx的关系,如果a[Mid]>x,由于序列是升序排列,所以区间[Mid,Right]都可以被排除,修改右边界Right=Mid-1
        ③如果a[Mid]<=x,修改左边界Left=Mid+1
    重复执行二分操作直到Left>Right
        下面我们来分析答案的情况。循环结束示意图如下:LeftRight大于x小于等于x

     最终循环结束时一定是Left=Right+1,根据二分第②步的做法我们知道Right的右边一定都是大于x的,而根据第③步我们可以知道Left左边一定是小于等于x的。
        所以,一目了然,最终答案是Right指向的数。Right=0就是题目中输出-1的情况。

    突然好想你,最长不下降子序列Nlogn

    【分析】:

    定义:a[1..n]为原始序列,d[k]表示长度为k的不下降子序列末尾元素的最小值,len表示当前已知的最长子序列的长度。

    初始化:d[1]=a[1]; len=1; 0个元素的时候特判一下)

    现在我们已知最长的不下降子序列长度为1,末尾元素的最小值为a[1],那么我们让i2n循环,依次求出前i个元素的最长不下降子序列的长度,循环的时候我们只需要维护好d这个数组还有len就可以了。

    关键问题就是怎么维护?

    可以看出我们是要用logn的复杂度维护的。实际上利用了d数组的一个性质:单调性。(长度更长了,d[k]的值是不会减小的)

    考虑新进来一个元素a[i]

    如果这个元素大于等于d[len],直接让d[len+1]=a[i],然后len++。这个很好理解,当前最长的长度变成了len+1,而且d数组也添加了一个元素。

    如果这个元素小于d[len]呢?说明它不能接在最后一个后面了。那我们就看一下它该接在谁后面。

    准确的说,并不是接在谁后面。而是替换掉谁。因为它接在前面的谁后面都是没有意义的,再接也超不过最长的len,所以是替换掉别人。那么替换掉谁呢?就是替换掉那个最该被它替换的那个。也就是在d数组中第一个大于它的。第一个意味着前面的都小于等于它。假设第一个大于它的是d[j],说明d[1..j-1]都小于等于它,那么它完全可以接上d[j-1]然后生成一个长度为j的不下降子序列,而且这个子序列比当前的d[j]这个子序列更有潜力(因为这个数比d[j]小)。所以就替换掉它就行了,也就是d[j]=a[i]。其实这个位置也是它唯一能够替换的位置(前面的替了不满足d[k]最小值的定义,后面替换了不满足不下降序列)

    至于第一个大于它的怎么找……STL upper_bound。每次复杂度logn

    【代码实现】:

    //最长不下降子序列nlogn  Song

        d[1]=a[1];  //初始化

        int len=1;

        for (int i=2;i<=n;i++)

        {

            if (a[i]>=d[len]) d[++len]=a[i];  

    //如果可以接在len后面就接上,如果是最长上升子序列,这里变成>

            else  //否则就找一个最该替换的替换掉

            {

                int j=upper_bound(d+1,d+len+1,a[i])-d;  

    //找到第一个大于它的d的下标,如果是最长上升子序列,这里变成lower_bound

                d[j]=a[i];

            }

    }

    【总结】:

      1. 个人感觉是递归的升级版,只不过是侵略性地排除答案,再一层一层地排查,速度很快
      2. 听说有优先队列这个东西了解一下,优先队列是什么,就是个堆,一个有顺序的数组

    感谢各位与信奥一本通的鼎力相助!

  • 相关阅读:
    XML中<beans>中属性概述
    (转)深入理解Java:注解(Annotation)自定义注解入门
    maven 配置参数详解!
    maven setting.xml文件配置详情
    hashMap与 hashTable , ArrayList与linkedList 的区别(详细)
    jdbc参数
    linux下ftp命令的安装与使用
    java中的Iterator与增强for循环的效率比较
    命令行窗口常用的一些小技巧
    在eclispe的类中快速打出main方法
  • 原文地址:https://www.cnblogs.com/SeanOcean/p/10975598.html
Copyright © 2011-2022 走看看