【省选】算法总结——线段树2

来源:互联网 发布:mac上microsoft 编辑:程序博客网 时间:2024/06/11 21:08

130331总结——线段树2

 

前面总结了一次线段树,基本上讲完了所有的线段树操作

1.      区间修改(懒标记)

2.      插入/修改/删除 元素

3.      查询维护信息

4.      静态维护动态区间 (size)

总之,Operation和MinMax两道题基本上已经包括完了

 

上次总结基本是操作,这次主要是刚做了两道题,很有起始,所以总结一下线段树维护信息的方式和一些建树方法

 

 

 

 


 

1.    信息维护

顾名思义,如果题目很明了是线段树,那么他一定会有一些处理的技巧。

顺便把上次没说到的再说一次

 

 

1.1 最(大/小)值

这个很简单,直接用一个minx[]数组即可【注:还有一些人喜欢把线段树的所有信息定义为一个结构体,如 struct node{int l,r,info;}tree[N*4]; 其中info就是存我们想要的信息,这里是minx,这样也是可以的】,由于我们维护线段树是递归调用,所以在回溯的时候更新信息,比如修改操作可以向下面这样写

void change(int p,int l,int r,int a,int b,int c)

//将区间[a,b]修改为c ,维护区间最小值ֵ

{

    if(a<=l&&b>=r){minx[p]=maxx[p]=c;return;}

    int m=(l+r)>>1;

    if(a<=m) change(p<<1,l,m,a,b,c);

    if(b>m) change((p<<1)+1,m+1,r,a,b,c);

    minx=min(minx[p<<1],minx[(p<<1)+1]);

}

其他查询操作同理

 

 

 

1.2 区间和

只需要把min[]数组改成sum[]即可,每次更新时sum[p]为左右儿子sum[]之和

 

 

 

1.3 区间中1的个数

这个时候就会用到懒标记,代码如下

void query(int p,int l,int r,int a,int b)

//找区间[a,b]有多少个1

//val[p]=1或0 表示区间[l,r]全部为1或0

//val[p]=-1 表示区间[l,r]中0和1均有

{

    if(a<=l&&b>=r)

    {

        if(val[p]!=-1)return val[p]*(r-l+1);

    }

    int m=(l+r)>>1,x1=0,x2=0;

    if(val[p]!=-1) //懒标记下传

    {

        val[p<<1]=val[(p<<1)+1]=val[p];

        val[p]=-1;

    }

    if(a<=m) x1=query(p<<1,l,m,a,b);

    if(b>m) x2=query((p<<1)+1,m+1,r,a,b);

    if(val[p]==-1&&val[p<<1]==val[(p<<1)+1])

        val[p]=val[p<<1]; //再把标记传上去

    return x1+x2; //返回两个区间1的个数和

}

 

 

 

1.4 区间最大连续1的长度

这个问题就可以借鉴operation那一题了,由于连续的1在线段树中可能跨区间,所以我们可以用一个last记录上次找到的是1还是0,然后用best记录最优值,用ans记录当前找到的连续1的长度

void data_up(int p)

{

    if(val[p<<1]==val[(p<<1)+1]&&val[p]==-1)

        val[p]=val[p<<1];

}

void data_down(int p)//mark downloading

{

    if(val[p]!=-1)

    {

        val[p<<1]=val[(p<<1)+1]=val[p];

        val[p]=-1;

    }

}

void find(int p,int l,int r,int a,int b)//find continuous 1

{

    if(a<=l && b>=r)

    {

        if(val[p]!=-1)

        {

            if(last==1&& val[p]==1)

            {

                ans+=r-l+1;//We must add the last calculation answer

                best=max(best,ans);

            }

            elseif(last==0&& val[p]==1)

            {

                ans=r-l+1;//We only need this calculation answer

                best=max(best,ans);

            }

            elseif(val[p]==0) ans=0;//renew the answer

            best=max(best,ans);

            last=val[p];

            return;

        }

        if(l==r)return;

    }

    data_down(p);

    int m=(l+r)>>1;

    if(a<=m) find(p<<1,l,m,a,b);

    if(b>m) find((p<<1)+1,m+1,r,a,b);

    data_up(p);

}

 

 

 

1.4 区间中最大连续子区间和

【小白逛公园】http://blog.csdn.net/jiangzh7/article/details/8745213

这一题看似dp,但是一看数据范围,很大,仔细看题,区间!所以,线段树!

但是怎么维护呢?建好树后每个区间来一个dp ?超时否?

所以另想办法。

1.      维护一个从最左开始的连续区间最大值L

2.      一个从最右开始向左的连续区间最大和R

3.      一个中间任意位置开始的连续区间最大值M

4.      最后再来一个区间和S

借鉴区间下dp的思路

如果我们把Q=[a,b]分成A=[a,m]和B=[m+1,b]两个区间,那么

1.      Q.S = A.S + B.S

2.      Q.L = max { A.L , A.S + B.L };

3.      Q.R = max { B.R , B.S + A.R };

4.      Q.M = max { Q.M , Q.M , A.R +B.L };

这样就可以维护线段树了,代码就不放了

 

还有一道应该比这个稍难吧,POJ上的,其实我NOIP集训的时候就做了,只不过是朴素过的50分

【POJ3667】http://blog.csdn.net/jiangzh7/article/details/8744258

 

 


 

2. 建树

暂时比较靠技巧的只见过两种,就把这两种说说吧

 

 

 

2.1 插入类

有一题是【[JSOI2008]最大数Maxnumber】

这一题本来很简单的,但是考试的时候不知道是哪根筋抽了,嗯是没看出来怎么做

虽然是说的插入元素,但是我们可以转换一下,先假设插入了MAXN个0,然后在进行修改,这样就ok了!

 

 

 

2.2 中国结类

这个名字是我自己取的。。。。。

主要是因为【RQ60美丽的中国结】一题,它的建树方法很是特别(至少我的第一次见),好在后来遇到了几次都很快的想到了这种方法

这一题只告诉你那些点是相连的,但是却不知道跟在哪里

dfs其实是把整棵树遍历一次,想想强连通那个时间戳dfstime ,我们可以类似的,把整棵树遍历一次,然后用L[x]和R[x]记录节点x进入dfs和退出dfs的时间,这样对于每个节点,我们都可以得到一个区间建立线段树了。

 

 

 

原创粉丝点击