线段树练习——区间合并
这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并(这里最难理解)
hdu 3308 http://acm.hdu.edu.cn/showproblem.php?pid=3308
题意:
给定n个数,下标从0-n-1,给出两种操作Q x,y询问区间[x,y]中的最长上升子序列(LCIS), U x,y 将下表为x的值替换成y(单点更新)。输出每次询问的值。
思路:
U操作的单点更新就不必多说了,这里关键理解的是区间的合并;
节点信息:
struct node { int l,r;//记录该节点的左右边界 int lm,rm,sm;//分别对应该点包括最左点的LCIS //包括最右点的LCIS,整个区间的LCIS }p[4*maxn];
区间的合并发生在左孩子区间的最右点<右孩子区间的最左点 这样两个区间就可以发生合并了。
lm=(lm==左孩子的长度)?左孩子的lm+右孩子的lm:左孩子的lm;
rm= (rm== 右孩子的长度) ?右孩子的rm+左孩子的rm:右孩子的rm;
sm=max(max(左孩子的sm,右孩子的sm),左孩子rm+右孩子的lm)
#include<cstdio> #include<iostream> #define maxn 100007 using namespace std; struct node { int l,r;//记录该节点的左右边界 int lm,rm,sm;//分别对应该点包括最左点的LCIS //包括最右点的LCIS,整个区间的LCIS }p[4*maxn]; int val[maxn]; void pushup(int rt) { //如果不存在区间合并的话正常的更新 p[rt].lm = p[rt<<1].lm; p[rt].rm = p[rt<<1|1].rm; p[rt].sm = max(p[rt<<1].sm,p[rt<<1|1].sm); int m = (p[rt].l + p[rt].r)>>1; //存在可合并的区间 if (val[m] < val[m + 1]) { int L = m - p[rt].l + 1; int R = p[rt].r - m; if (p[rt<<1].lm == L) p[rt].lm += p[rt<<1|1].lm; if (p[rt<<1|1].rm == R) p[rt].rm += p[rt<<1].rm; p[rt].sm = max(p[rt].sm,p[rt<<1].rm + p[rt<<1|1].lm); } } void build(int l,int r,int rt) { p[rt].l = l; p[rt].r = r; p[rt].lm = p[rt].rm = p[rt].sm = 1; if (l == r) { scanf("%d",&val[l]); return ; } int m = (l + r)>>1; build(l,m,rt<<1); build(m + 1,r,rt<<1|1); pushup(rt); } void update(int pos,int sc,int rt) { if (p[rt].l == p[rt].r) { val[p[rt].l] = sc; return ; } int m = (p[rt].l + p[rt].r)>>1; if (pos <= m) update(pos,sc,rt<<1); else update(pos,sc,rt<<1|1); pushup(rt); } int query(int L,int R,int rt) { if (p[rt].l >= L && p[rt].r <= R) return p[rt].sm; int m = (p[rt].l + p[rt].r)>>1; int res = 0; if (L <= m) res = max(res,query(L,R,rt<<1)); if (R > m) res = max(res,query(L,R,rt<<1|1)); //分出的两个小区间可以合并,记住这里有L,R左右临界的限制。 if (val[m] < val[m + 1]) res = max(res,min(m - L + 1,p[rt<<1].rm) + min(R - m,p[rt<<1|1].lm)); return res; } /*int query(int l,int r,int rt) { if(p[rt].l==l&&p[rt].r==r) return p[rt].sm; int m=(p[rt].l+p[rt].r)>>1; if(r<=m) return query(l,r,rt<<1); if(l>m) return query(l,r,rt<<1|1); int hhm=max(query(l,m,rt<<1),query(m+1,r,rt<<1|1)); if(val[m]<val[m+1]) hhm=max(hhm,min(m-l+1,p[rt<<1].rm)+min(r-m,p[rt<<1|1].lm)); return hhm; }*/ int main() { //freopen("3308.txt","r",stdin); int t,x,y,n,q; char op[2]; scanf("%d",&t); while (t--) { scanf("%d%d",&n,&q); build(0,n - 1,1); while (q--) { scanf("%s%d%d",op,&x,&y); if (op[0] == 'Q') printf("%d\n",query(x,y,1)); else update(x,y,1); } } return 0; }
uestc 1425 http://acm.uestc.edu.cn/problem.php?pid=1425
代码还没提交,指引本oj今天打不开
题意:
和上题题意一样不同的是这里不是单纯的更新一个点了,而是更新一段区间。就相当于区间合并+成段更新(上题是区间合并+单点更新)
思路:
同上,只不过在处理区间合并的判断条件时有所不同,上题因为每次都能更新到叶节点所以不必担心lz标记还没更新下来这一说。而这里lz大多数情况下不能更新到叶节点也就导致在判断区间合并的条件时产生了难点,才开始的时候我记录了每个树上节点总共加了多少add[rt],样例过了可是老是wa无语了,后来想了想,如果我的子区间被更新后那么我只记录了子区间加了多少,而区间没有被更新,所以add记录的就是错误的信息了。
这里我们记录每个区间的左右点的值,每次更新的时候往上更新就好了,这样就保证了每个区间的左右端点值的正确性,因为我们在区间合并时需要的只是端点。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <stack> #include <set> #include <map> #include <string> #define CL(a,num) memset((a),(num),sizeof(a)) #define iabs(x) ((x) > 0 ? (x) : -(x)) #define Min(a,b) (a) > (b)? (b):(a) #define Max(a,b) (a) > (b)? (a):(b) #define ll long long #define inf 0x7f7f7f7f #define MOD 100000007 #define lc l,m,rt<<1 #define rc m + 1,r,rt<<1|1 #define pi acos(-1.0) #define test puts("<------------------->") #define maxn 100007 #define M 100007 #define N 100007 using namespace std; struct node{ int lm,rm,sm; int l,r; int al,ar;//这里记录左右端点信息 int mid(){ return (l + r)>>1; } }tre[N<<2]; int val[N],lz[N<<2],add[N<<2]; void pushup(int rt,int Ls,int Rs,int m){ tre[rt].lm = tre[rt<<1].lm; tre[rt].rm = tre[rt<<1|1].rm; tre[rt].sm = max(tre[rt<<1].sm,tre[rt<<1|1].sm); tre[rt].al = tre[rt<<1].al; tre[rt].ar = tre[rt<<1|1].ar; if (tre[rt<<1].ar < tre[rt<<1|1].al){ if (tre[rt].lm == Ls) tre[rt].lm += tre[rt<<1|1].lm; if (tre[rt].rm == Rs) tre[rt].rm += tre[rt<<1].rm; tre[rt].sm = max(tre[rt].sm,tre[rt<<1].rm + tre[rt<<1|1].lm); } } void pushdown(int rt){ if (lz[rt] != 0){ lz[rt<<1] += lz[rt]; lz[rt<<1|1] += lz[rt]; tre[rt<<1].al += lz[rt]; tre[rt<<1].ar += lz[rt]; tre[rt<<1|1].al += lz[rt]; tre[rt<<1|1].ar += lz[rt]; lz[rt] = 0; } } void build(int l,int r,int rt){ lz[rt] = 0; tre[rt].l = l; tre[rt].r = r; tre[rt].ar = tre[rt].al = 0; if (l == r){ tre[rt].lm = tre[rt].rm = tre[rt].sm = 1; scanf("%d",&tre[rt].al); tre[rt].ar = tre[rt].al; return ; } int m = tre[rt].mid(); build(lc); build(rc); pushup(rt,m - l + 1,r - m,m); } void update(int L,int R,int sc,int rt){ if (tre[rt].l >= L && tre[rt].r <= R){ lz[rt] += sc; tre[rt].al += sc; tre[rt].ar += sc; return ; } int m = tre[rt].mid(); pushdown(rt); if (L <= m) update(L,R,sc,rt<<1); if (R > m) update(L,R,sc,rt<<1|1); pushup(rt,m - tre[rt].l + 1,tre[rt].r - m,m); } int query(int L,int R,int rt){ if (tre[rt].l >= L && tre[rt].r <= R){ return tre[rt].sm; } pushdown(rt); int res = 0; int m = tre[rt].mid(); if (L <= m) res = max(res,query(L,R,rt<<1)); if (R > m) res = max(res,query(L,R,rt<<1|1)); if (tre[rt<<1].ar < tre[rt<<1|1].al){ res = max(res,min(m - L + 1,tre[rt<<1].rm) + min(R - m,tre[rt<<1|1].lm)); } pushup(rt,m - tre[rt].l + 1,tre[rt].r - m,m); return res; } int main(){ //freopen("data.in","r",stdin); int cas = 1; int T,n,q; char op[3]; int x,y,z; scanf("%d",&T); while (T--){ printf("Case #%d:\n",cas++); scanf("%d%d",&n,&q); build(1,n,1); while (q--){ scanf("%s%d%d",op,&x,&y); if (op[0] == 'a'){ scanf("%d",&z); update(x,y,z,1); } else{ printf("%d\n",query(x,y,1)); } } } return 0; }
pku 3667 http://poj.org/problem?id=3667
题意:
旅游团入住旅馆,旅馆的房间在这里形式化为X正半轴上的点,来到的团队必须住在连续的房间内,有两种操作1 x就是旅游团入住在连续的x个房间内,2 xi di 旅游团将房间xi - xi + di - 1的房间退掉;求每次入住连续的x个房间时的起始房间号。
思路:
这里入住的话肯定是连续的房间,也即连续的点。线段树区间的合并问题,这里节点记录该区间的最大连续节点数,每次入住就是一个查询满足条件区间的过程,这里注意左右孩子区间合并时满足的条件。向上更新时也是要判断区间的合并。
#include <iostream> #include <cstring> #include <cstdio> #define maxn 50007 using namespace std; int msum[4*maxn], lsum[4*maxn],rsum[4*maxn]; int lz[4*maxn]; void pushup(int rt,int m) { lsum[rt] = lsum[rt<<1]; rsum[rt] = rsum[rt<<1|1]; msum[rt] = max(msum[rt<<1],msum[rt<<1|1]); //注意区间的合并 if (lsum[rt<<1] == m - (m>>1)) lsum[rt] += lsum[rt<<1|1]; if (rsum[rt<<1|1] == (m>>1)) rsum[rt] += rsum[rt<<1]; msum[rt] = max(msum[rt],rsum[rt<<1] + lsum[rt<<1|1]); } void pushdown(int rt,int m) { if (lz[rt] != -1) { lz[rt<<1] = lz[rt<<1|1] = lz[rt]; msum[rt<<1] = lsum[rt<<1] = rsum[rt<<1] = lz[rt] == 0? 0 : m - (m>>1); msum[rt<<1|1] = lsum[rt<<1|1] = rsum[rt<<1|1] = lz[rt] == 0? 0: (m>>1); lz[rt] = -1; } } void build(int l,int r,int rt) { lz[rt] = -1; if (l == r) { msum[rt] = lsum[rt] = rsum[rt] = 1; return ; } int m = (l + r)>>1; build(l,m,rt<<1); build(m + 1,r,rt<<1|1); pushup(rt,r - l + 1); } void update(int L,int R,int mk,int l,int r,int rt) { if (l >= L && r <= R) { lz[rt] = mk; msum[rt] = lsum[rt] = rsum[rt] = mk == 0? 0: (r - l + 1); return ; } pushdown(rt,r - l + 1); int m = (l + r)>>1; if (L <= m) update(L,R,mk,l,m,rt<<1); if (R > m) update(L,R,mk,m + 1,r,rt<<1|1); pushup(rt,r - l + 1); } int query(int sc,int l,int r,int rt) { if (l == r) return l; pushdown(rt,r - l + 1); int m = (l + r)>>1; if (sc <= msum[rt<<1]) return query(sc,l,m,rt<<1); else if (rsum[rt<<1] + lsum[rt<<1|1] >= sc) return m - rsum[rt<<1] + 1;//注意合并区间时满足的情况 return query(sc,m + 1,r,rt<<1|1); } int main() { //freopen("d.in","r",stdin); int n,m,x,y,z; scanf("%d%d",&n,&m); build(1,n,1); while (m--) { scanf("%d%d",&x,&y); if (x == 1) { if (msum[1] < y) puts("0"); else { int r = query(y,1,n,1);//找到起点 update(r,r + y - 1,0,1,n,1);//然后将该区间更新 printf("%d\n",r); } } else { scanf("%d",&z); update(y,y + z - 1,1,1,n,1);//更新该区见 } } return 0; }
pku 3368 Frequent values http://poj.org/problem?id=3368
本来结束了线段树的刷题去做rmq来看到了这道题目,rmq没想出来可能是最近做线段树的题目比较多吧,想到了线段树解法。
题意:
给定长度为n的不降序列,求询问区间[s,e]内出现频率最高的频率;
思路:
典型的区间合并做法,只有合并时的条件val[m] = val[m + 1]只满足这一条件还不够,只有lsum = L rsum = R 时才能进行合并 ,还有在query求值时写错了,是求最大值不是求左右值。
这题看解题报告大多数都是先离散化后再用rmq或者线段树做的,我直接就区间合并了,少了离散化的处理,不过效率可能有点低。
#include <cstdio> #include <cstring> #include <iostream> #include <cmath> #define maxn 100007 using namespace std; int msum[4*maxn],lsum[4*maxn],rsum[4*maxn]; int val[maxn]; void pushup(int rt,int m,int len) { lsum[rt] = lsum[rt<<1]; rsum[rt] = rsum[rt<<1|1]; msum[rt] = max(msum[rt<<1],msum[rt<<1|1]); if (val[m] == val[m + 1]) { int L = len - (len>>1); int R = len>>1; if (lsum[rt] == L)//第一个错误点,掉下了这个条件 lsum[rt] += lsum[rt<<1|1]; if (rsum[rt] == R) rsum[rt] += rsum[rt<<1]; msum[rt] = max(msum[rt],rsum[rt<<1] + lsum[rt<<1|1]); } } void build(int l,int r,int rt) { if (l == r) { scanf("%d",&val[l]); msum[rt] = lsum[rt] = rsum[rt] = 1; return ; } int m = (l + r)>>1; build(l,m,rt<<1); build(m + 1,r,rt<<1|1); pushup(rt,m,r - l + 1); } int query(int L,int R,int l,int r,int rt) { if (l >= L && r <= R) { //printf("%d %d\n",l,r); return msum[rt]; } int res = 0; int m = (l + r)>>1; if (L <= m) res = max(res,query(L,R,l,m,rt<<1));//第二个错误点掉下max函数了 if (R > m) res = max(res,query(L,R,m +1,r,rt<<1|1)); if (val[m] == val[m + 1]) res = max(res,min(m - L + 1,rsum[rt<<1]) + min(R - m,lsum[rt<<1|1])); return res; } void set(int l,int r,int rt) { printf("%d %d %d %d %d\n",l,r,msum[rt],lsum[rt],rsum[rt]); if (l == r) { return ; } int m = (l + r)>>1; set(l,m,rt<<1); set(m + 1,r,rt<<1|1); } int main() { //freopen("d.txt","r",stdin); int n,q; int s,e; while (~scanf("%d",&n)) { if (!n) break; scanf("%d",&q); build(1,n,1); while (q--) { scanf("%d%d",&s,&e); printf("%d\n",query(s,e,1,n,1)); } //set(1,n,1); } return 0; }
pku Tunnel Warfare http://poj.org/problem?id=2892 && hdu 1540 http://acm.hdu.edu.cn/showproblem.php?pid=1540
题意:
在抗日战争时期的地道战,很是出名,这里给出n个地道,他们是相互连同在一条通道上的,然后给出q个操作,分别有
D x经编号为x的地道摧毁, Q x 询问x可以连同的地道数目 R恢复前一个被摧毁的地道。
思路:
这里主要是记录每个点对应的lsum rsum难点在于Q询问时的操作,以前做的都是区间里面的询问这里是对点的询问,对点的询问时,我们只要找到
pos >= m - rsum[rt<<1] + 1 && pos <= m + lsum[rt<<1|1] 区间[m - rsum[rt<<1] + 1,pos <= m + lsum[rt<<1|1] 即可返回该区间的长度;
#include <cstdio> #include <cstring> #include <cstdlib> #include <map> #define maxn 50005 using namespace std; int lsum[4*maxn],rsum[4*maxn]; int stack[maxn],top; void pushup(int rt,int m) { lsum[rt] = lsum[rt<<1]; rsum[rt] = rsum[rt<<1|1]; if (lsum[rt] == (m - (m>>1))) lsum[rt] += lsum[rt<<1|1]; if (rsum[rt] == (m>>1)) rsum[rt] += rsum[rt<<1]; } void build(int l,int r,int rt) { lsum[rt] = rsum[rt] = r - l + 1; if (l == r) return ; int m = (l + r)>>1; build(l,m,rt<<1); build(m + 1,r,rt<<1|1); } void update(int pos,int sc,int l,int r,int rt) { if (l == r) { lsum[rt] = rsum[rt] = sc; return ; } int m = (l + r)>>1; if (pos <= m) update(pos,sc,l,m,rt<<1); else update(pos,sc,m + 1,r,rt<<1|1); pushup(rt,r - l + 1); } int query(int pos,int l,int r,int rt) { if (l == r) return 0; int m = (l + r)>>1; if (pos >= m - rsum[rt<<1] + 1 && pos <= m + lsum[rt<<1|1]) return rsum[rt<<1] + lsum[rt<<1|1]; if (pos <= m) return query(pos,l,m,rt<<1); else return query(pos,m + 1,r,rt<<1|1); } int main() { //freopen("d.txt","r",stdin); int n,q; int x; char op[3]; while (~scanf("%d%d",&n,&q)) { top = 0; build(1,n,1); while (q--) { scanf("%s",op); if (op[0] == 'D') { scanf("%d",&x); stack[++top] = x; update(x,0,1,n,1); } else if (op[0] == 'Q') { scanf("%d",&x); printf("%d\n",query(x,1,n,1)); } else { if (top) { int pos = stack[top--]; update(pos,1,1,n,1); } } } } return 0; }