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.youkuaiyun.com/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.youkuaiyun.com/jiangzh7/article/details/8744258
2. 建树
暂时比较靠技巧的只见过两种,就把这两种说说吧
2.1 插入类
有一题是【[JSOI2008]最大数Maxnumber】
这一题本来很简单的,但是考试的时候不知道是哪根筋抽了,嗯是没看出来怎么做
虽然是说的插入元素,但是我们可以转换一下,先假设插入了MAXN个0,然后在进行修改,这样就ok了!
2.2 中国结类
这个名字是我自己取的。。。。。
主要是因为【RQ60美丽的中国结】一题,它的建树方法很是特别(至少我的第一次见),好在后来遇到了几次都很快的想到了这种方法
这一题只告诉你那些点是相连的,但是却不知道跟在哪里
dfs其实是把整棵树遍历一次,想想强连通那个时间戳dfstime ,我们可以类似的,把整棵树遍历一次,然后用L[x]和R[x]记录节点x进入dfs和退出dfs的时间,这样对于每个节点,我们都可以得到一个区间建立线段树了。