这类题目会询问区间中满足条件的连续最长区间,所以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)

View Code

#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));elseupdate(x,y,1);}}return 0;
}

uestc 1425 http://acm.uestc.edu.cn/problem.php?pid=1425

代码还没提交,指引本oj今天打不开

题意:

和上题题意一样不同的是这里不是单纯的更新一个点了,而是更新一段区间。就相当于区间合并+成段更新(上题是区间合并+单点更新)

思路:

同上,只不过在处理区间合并的判断条件时有所不同,上题因为每次都能更新到叶节点所以不必担心lz标记还没更新下来这一说。而这里lz大多数情况下不能更新到叶节点也就导致在判断区间合并的条件时产生了难点,才开始的时候我记录了每个树上节点总共加了多少add[rt],样例过了可是老是wa无语了,后来想了想,如果我的子区间被更新后那么我只记录了子区间加了多少,而区间没有被更新,所以add记录的就是错误的信息了。

这里我们记录每个区间的左右点的值,每次更新的时候往上更新就好了,这样就保证了每个区间的左右端点值的正确性,因为我们在区间合并时需要的只是端点。

View Code

#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个房间时的起始房间号。

思路:

这里入住的话肯定是连续的房间,也即连续的点。线段树区间的合并问题,这里节点记录该区间的最大连续节点数,每次入住就是一个查询满足条件区间的过程,这里注意左右孩子区间合并时满足的条件。向上更新时也是要判断区间的合并。

View Code

#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或者线段树做的,我直接就区间合并了,少了离散化的处理,不过效率可能有点低。

View Code

#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] 即可返回该区间的长度;

View Code

#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;
}

转载于:https://www.cnblogs.com/E-star/archive/2012/07/26/2609475.html

线段树练习——区间合并相关推荐

  1. [HDOJ3308]LCIS(线段树,区间合并)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3308 题意:给定n个数,两个操作: U A B:将位置A的数值改成B Q A B:查询[A,B]内最长 ...

  2. hdu 5052 树链剖分+线段树+区间合并

    各种裸,但合在一起好恶心... 因为路径是有向的,所以要维护区间里向两个方向走的最大收益和区间里的最大最小值,要用到区间合并的线段树 #include <iostream> #includ ...

  3. 【线段树_区间合并_异或翻转】HDU 3911 Black And White

    HDU 3911 Black And White 题意:有一个只有0和1组成的序列.对这个序列有两个操作:(1)对区间进行翻转.也就是0变1,1变0.  (2)查询区间连续1的序列的最大长度 区间合并 ...

  4. 喵哈哈村的冒菜店-(线段树的区间合并)

    喵哈哈村的冒菜店 发布时间: 2017年3月19日 16:00   最后更新: 2017年3月19日 16:01   时间限制: 1000ms   内存限制: 128M 描述 喵哈哈村的冒菜店开张了, ...

  5. hdu 3911 线段树+区间合并

    模板题 #include <iostream> #include <cstdio> #include <algorithm> #include <cstrin ...

  6. Codeforces Round #742 (Div. 2) E. Non-Decreasing Dilemma (线段树维护区间连续问题)

    题意: 操作1:把x位置的数字修改成y. 操作2:查询[l,r]之间不下降序列的个数. 题解: 线段树维护区间和问题 (这是套路,想不到只能说做题少别打我) . 用五个变量进行维护. sum区间总个数 ...

  7. 线段树分裂与合并 ----- P2824 [HEOI2016/TJOI2016]排序 [线段树分裂合并 OR 01序列排序+二分线段树]

    题目链接 题目大意: 对一个序列,每次按照升序或者降序排序序列某一段,问你最后的序列是什么? 解法1:二分+线段树 首先我们知道对一个01序列进行排序是很快的!我们只要知道里面有多少个1和多少个0,那 ...

  8. BZOJ1018 | SHOI2008-堵塞的交通traffic——线段树维护区间连通性+细节

    [题目描述] BZOJ1018 | SHOI2008-堵塞的交通traffic 有一天,由于某种穿越现象作用,你来到了传说中的小人国.小人国的布局非常奇特,整个国家的交通系统可 以被看成是一个2行C列 ...

  9. 【线段树】区间修改(区间覆盖、区间权值加)标记下放操作的逻辑顺序

    洛谷传送门:月下"毛景树" 由于没有合适的题目,就从这道题入手,解此题时用到的算法/数据结构包括: 树链剖分 线段树(区间覆盖.区间加.区间查询.单点修改) 这道题被我调试了四个小 ...

最新文章

  1. MySQL中Order By与Limit不要一起用
  2. Visio替代图表工具 - 为什么Visual Paradigm Online?
  3. Keil编译产生的RO,RW和ZI是什么
  4. 22、输入和输出重定向,管道,命令连接符,命令替换符
  5. c# webservice生成客户端及使用时碰到decimal类型时的特殊处理
  6. voc_eval.py:41: RuntimeWarning: invalid value encountered in greater_equal if np.sum(rec = t) ==
  7. 337. House Robber III 打家劫舍 III
  8. vue-router.esm.js?fe87:16 [vue-router] Route with name 'page' does not exist
  9. api商品分享源码_谈谈微服务中的 API 网关(API Gateway)
  10. mysql 层级结构查询
  11. NPOI读写Excel sheet操作
  12. fusionjs 学习二 核心概念
  13. 那位熟读戈培尔语录的“同仁” 我来与您做次探讨
  14. vue登录如何存储cookie_vue登录模块,登录状态应该存在哪里,怎么防止手动改cookie、localStorage?...
  15. 服务器内存傲腾基本参数信息,服务器傲腾内存
  16. DIY智能车赛优胜奖开源作品——手机遥控麦克纳姆轮小车(1)
  17. 工作效率的提升——如何高效沟通,有效降低沟通成本
  18. 给你三个必须要学C语言的理由!
  19. Designing Specification
  20. 托福高频真词List16 // 附托福TPO阅读真题

热门文章

  1. 业内首款云原生技术中台产品云原生 Stack 来了!
  2. 高德 Serverless 平台建设及实践
  3. OpenKruise v0.8.0 核心能力解读:管理 Sidecar 容器的利器
  4. 专访 Christian Posta:Istio 1.7 将成为生产可用的最稳定版本
  5. Apache Flink 1.10.0 发布 | 云原生生态周报 Vol. 38
  6. 如何判断数组所有数都不等于一个数_【每日算法Day 91】求解数组中出现次数超过1/3的那个数
  7. razor 怎样使用session变量_Nginx负载均衡解决session一致性问题
  8. 项目代码从GitHub上克隆到本地
  9. DICOM医学图像处理:DICOM存储操作之“多幅BMP图像数据存入DCM文件”
  10. 【赠书】pandas创始人手把手教你利用Python进行数据分析