分治

分治

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

    你们玩过猜数字的游戏吗?你的朋友心里想一个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. 听说有优先队列这个东西了解一下,优先队列是什么,就是个堆,一个有顺序的数组

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

转载于:https://www.cnblogs.com/SeanOcean/p/10975598.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
分治算法是一种递归的问题解决方法,它将一个问题划分为多个小问题,然后将这些小问题的解合并起来得到原问题的解。在Python,可以使用以下步骤来实现分治算法: 1. 定义基本情况:确定问题的基本情况,即无需再分解的情况。这通常是一个简单的问题,可以直接求解。 2. 划分问题:将原问题划分为多个子问题。通常,这是通过对原问题进行适当的分割或切割来实现的。 3. 递归求解:对每个子问题递归地应用相同的步骤,直到达到基本情况。这将解决每个子问题并获得其解。 4. 合并子问题的解:将子问题的解合并起来,得到原问题的解。 下面是一个Python实现的示例,演示了如何使用分治算法来求解的最大值: ```python def find_max(arr): # 基本情况:组只有一个元素时,返回该元素 if len(arr) == 1: return arr[0] # 划分问题:将组划分为两个子组 mid = len(arr) // 2 left_arr = arr[:mid] right_arr = arr[mid:] # 递归求解:分别对左右子组应用相同步骤 max_left = find_max(left_arr) max_right = find_max(right_arr) # 合并子问题的解:返回左右子组最大值的较大者 return max(max_left, max_right) # 示例用法 arr = [5, 9, 3, 1, 7] print(find_max(arr)) # 输出: 9 ``` 这个示例,我们使用分治算法来找到给定组的最大值。首先,我们定义了基本情况(即组只有一个元素时,直接返回该元素)。然后,我们将组分为两个子组,并对每个子组递归地应用相同的求解步骤。最后,我们通过比较左右子组的最大值来合并子问题的解,从而得到原问题的解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值