【总结】【线段树】2016.1.24CXB

来源:互联网 发布:百度关键词挖掘软件 编辑:程序博客网 时间:2024/06/09 02:58
     这几天我们学了线段树这个算法,而作为寒假上课的最后一天,自然要将这个算法再演化一下。今天我们就讲了线段树的一些延伸。

    最长不下降序列(nlogn):
【例题】
    有n个数排成一列,从前往后找出一条最长不下降序列,求这条序列的长度。(1<n<10^6)每个数字不超过10^6。
【输入】
5
1 2 1 3 8
【输出】
4

    这道题目我们很熟悉,以前讲动归的时候有,是n^2的算法,但学了线段树我们可以优化一下。

    首先证明一个东西:

O(nlogn)的算法关键是它建立了一个数组temp[],temp[i]表示长度为i的不下降序列中结尾元素的最小值,用top表示数组目前的长度,算法完成后top的值即为最长不下降子序列的长度。
设当前的以求出的长度为top,则判断num[i]和temp[top]:
1.如果num[i]>=temp[top],即num[i]大于长度为top的序列中的最后一个元素,这样就可以使序列的长度增加1,即top++,然后现在的temp[top]=num[i];
2.如果num[i]<temp[top],那么就在temp[1]...temp[top]中找到最大的j,使得temp[j]<num[i],然后因为temp[j]<num[i],所以num[i]大于长度为j的序列的最后一个元素,那么就可以更新长度为j+1的序列的最后一个元素,即temp[j+1]=num[i]。

摘自:http://www.cnblogs.com/wuyiqi/archive/2011/11/20/2256537.html


    我们要想,线段树是要有分段的,那怎样才能分段呢?从题目上看,每个数字范围很小,那么我们可不可以用数字来建立一棵线段树呢?
    答案是肯定的。当一个数字进来时,我们只需要看看从1到这个数字中,最大的长度是多少,然后将这个数字代表的点等于这个最大长度加一,用线段树就很容易维护,只需要在一开始将全部设为0即可。
    
    换个角度,假如这个数字很大怎么办?
    其实仔细想,其实数字的大小并没有什么关系,其实我们只需要将每个数字排序,然后用这些数字一个一个排下去建立线段树即可。但注意的是,我们要记下每个数字的位置。
    对于这个位置怎么记录,老师的我没怎么听0.0,自己想到的是可以用结构体,在构建线段树的时候顺便记下就行了。(这个不是重点)

那么我们来讲第二种算法吧,二分:
    前面已经证明了最长不下降序列的单调性,事实上,当长度为3的序列中,结尾有两种情况,一个是9,一个是7是,自然,我们会选择更小的一个。也就是说,我们可以开一个数组,数组的下标就是序列长,而记录的值则是当前长度的最小值。我来举个栗子:
【输入】
7
3 2 5 1 6 4 9
【输出】
4【总结】【线段树】2016.1.24CXB - 李kuandui - 神殇KD灬度阡陌
 

    画错位置了......蓝色全部向右移一格。
    从图片中我们就能很清晰地看出这个算法的原理了~
    那么接下来,我们就需要考虑一个问题了——如何找到并将一个数写入表格?
    这要牵扯到一个神奇的函数——upper_bound!这是什么意思呢就是......的数的下标,具体用法详见http://www.cplusplus.com/(很好用)——我后面贴了用法。
    怎么做自己想吧!

算了我自己贴= =:

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int oo=100000007;int n;int num[1000001];int lon[1000001];int ans;int main(){freopen("up_longest.in","r",stdin);freopen("up_longest.out","w",stdout);scanf("%d",&n);for(int i=0;i<n;++i)scanf("%d",&num[i]);for(int i=1;i<=n;++i)lon[i]=oo;lon[0]=-oo;ans=0;for(int i=0;i<n;++i){int tmp=upper_bound(lon,lon+n,num[i])-lon;if(lon[tmp]==oo)lon[tmp]=num[i];elseif(lon[tmp]>num[i])lon[tmp]=num[i];if(tmp>ans)ans=tmp;}printf("%d\n",ans);return 0;}





好了我先讲这么多.....下午继续更


                                             ====================下午===================

发现一个简洁明了的用法,于是
转载:http://blog.sina.com.cn/s/blog_62582b7e0100eyqz.html

#include <iostream>#include <algorithm>//必须包含的头文件using namespace std;int main()

{ int point[10] = {1,3,7,7,9}; int tmp = upper_bound(point, point + 5, 7) - point;//按从小到大,7最多能插入数组point的哪个位置,即最后一个小于等于它的数的位置+1 printf("%d\n",tmp); tmp = lower_bound(point, point + 5, 7) - point;////按从小到大,7最少能插入数组point的哪个位置,即第一个大于等于它的数的位置-1 printf("%d\n",tmp); return 0;}

一篇讲这个的博客:http://www.cnblogs.com/cobbliu/archive/2012/05/21/2512249.html
    【例题】
    现在给出一棵树(注意只是【树】),有两种操作:
一、将一棵子树全部加上一个值
二、求这棵树上的最大值

    这道题目看起来十分复杂,因为这是一棵【树】,而不是【二叉树】。那么我们应该怎么做?
    我们先画出一个图:
【总结】【线段树】2016.1.24CXB - 李kuandui - 神殇KD灬度阡陌
    这是一棵很简单的树,我们发现,如果要将其转为二叉树显然是不可能的,所需步数太多了!
    但是,如果我们考虑线段树呢?又该怎么建立?
    答案就是DFS!
    众所周知,对于DFS,他的每个非叶节点,至少都会经过两次,so——我们只需要找到一个点在DFS中出现的第一次和最后一次,就能知道它的子树里包含有什么节点。
    拿图来说,它的DFS遍历是:1242526213731,去除多余的得:1245623731。这就是一段数据,我们就可以用线段树来维护了!
    这个做法其实在之前就已经讲过,就是图论里一个十分重要的东西——时间戳(时间标记)!这说明,我们算法之间是共同的,有时新的知识可以通过结合旧知识来得到更多东西,培养思维十分重要啊!
 
    
0 0
原创粉丝点击