线段树模板:点修改,区间修改
来源:互联网 发布:word mac破解版下载 编辑:程序博客网 时间:2024/04/29 17:24
最近在看《算法竞赛入门经典训练指南》, 感觉以前的想法几乎完全是错的,模板并不一定能直接套。
最近在看线段树,才知道线段树每个节点的附加信息才是重头戏,因此完全套模板是不可行的。但是思想方法可以借鉴,并在此基础上 加以改进。
1.点修改
给出一个有n个元素的数组A[1],A[2],……A[n]。任务是设计一个数据结构,支持以下两种操作:
①update(x,v):把A[x]修改为v。
②Query(L,R):计算min{A[L],A[L+1],……A[R]}。
#include <algorithm>#include <cctype>#include <cmath>#include <cstdio>#include <cstdlib>#include <cstring>#include <iomanip>#include <iostream>#include <map>#include <queue>#include <string>#include <set>#include <vector>#include<cmath>#include<bitset>#include<sstream>using namespace std;#define INF 0x7fffffff#define maxn 100005#define maxnode 200005//线段树的节点编号是1-nint ql,qr;//查询[ql,qr]中的最小值int p,v;//修改,A[p]=v;struct IntervalTree{ int minv[maxnode]; //minv[o]表示节点o所对应区间中所有元素的最小值 //查询[ql,qr]中的最小值 int query(int o,int L,int R){ int M=L+(R-L)/2; int ans=INF; if(ql<=L&&R<=qr) return minv[o];//当前节点完全包含在查询区间内 if(ql<=M) ans=min(ans,query(o*2,L,M));//往左走 if(qr>M) ans=min(ans,query(o*2+1,M+1,R)); return ans; } //修改:A[p]=v; void update(int o,int L,int R){ int M=L+(R-L)/2; if(L==R) minv[o]=v;//叶节点直接更新minv else { //L<R //先递归更新左子树或者右子树 if(p<=M){ update(2*o,L,M); } else{ update(2*o+1,M+1,R); } //计算本节点的minv minv[0]=min(minv[2*o],minv[2*o+1]); } }};IntervalTree tree;int main(){ int n,m; while(scanf("%d%d",&n,&m)==2){ memset(&tree,0,sizeof(tree)); int op; for(int i=1;i<=n;i++){ scanf("%d",&v); p=i; tree.update(1,1,n);//建树的过程 } while(m--){ scanf("%d",&op); if(op==1){ scanf("%d%d",&p,&v); tree.update(1,1,n);//修改树节点 } else{ scanf("%d%d",&ql,&qr);//修改查询区间 printf("%d\n",tree.query(1,1,n)); } } } return 0;}
2.区间修改
1.快速区间操作1
1.操作:
给出一个n个元素的数组A[1],A[2],……A[n],你的任务是设计一个数据结构支持以下两种操作:
Add(L,R,v):把A[L],A[L+1],……A[R]的值全部增加v;
Query(L,R):计算子序列A[L],A[L+1],……A[R]的元素和,最小值和最大值。
点修改只会影响logn个结点,但是区间修改,在最坏情况下会影响树中的所有结点。前面讲过,任意区间都能分解成不超过2h个不想交的区间的并。利用这个结论,可以化整为零。
2.维护结点的信息
前面已经讲过,如果仍然用sum[o]表示结点o对应的区间中所有数之和,则add操作最坏情况下可能会修改所有的sum。解决办法是把sum[o]的定义改成“如果只执行结点o及其子孙结点中的add操作,结点o对应区间中所有数之和”。这样附加信息可以方便的维护,而且每个原始add所影响的结点数目变成了O(h)。代码如下:
//维护信息 //维护结点o,它对应的区间是[L,R] void maintain(int o,int L,int R){ int lc=2*o,rc=2*o+1; sumv[o]=minv[o]=maxv[o]=0; if(R>L){ //考虑左右子树 sumv[o]=sumv[lc]+sumv[rc]; minv[o]=min(minv[lc],minv[rc]); maxv[o]=max(maxv[lc],maxv[rc]); } //考虑add操作 minv[o]+=addv[o]; maxv[o]+=addv[o]; sumv[o]+=addv[o]*(R-L+1); }
3.修改操作
在进行add操作时,哪些结点需要调用上述maintain函数呢?很简单,递归访问到的所有结点全部要调用,并且是在递归返回后调用。代码如下:
//修改操作 void update(int o,int L,int R){ int lc=2*o,rc=2*o+1; if(qL<=L&&R<=qR){ //递归边界 addv[o]+=v;//累加边界的add值 } else{ int M=L+(R-L)/2; if(qL<=M) update(lc,L,M); if(qR>M) update(rc,M+1,R); } //递归结束前重新计算本结点的附加信息 maintain(o,L,R); }
4.查询操作
仍然是把查询区间递归分解成若干个不相交的子区间,把各个子区间的查询结果加以合并,但需要注意的是每个边界区间的结果不能直接用,还得考虑祖先结点对它的影响。
为了方便,我们在递归函数中增加一个参数,表示当前区间的所有祖先结点的add之和。代码如下:
void query(int o,int L,int R,int add){ if(qL<=L&&R<=qR){ //递归边界:用边界区间的附加信息更新答案 _sum+=sumv[o]+add*(R-L+1); _min=min(_min,minv[o]+add); _max=max(_max,maxv[o]+add); } else{ //不递归统计,累加参数add int M=L+(R-L)/2; if(qL<=M) query(2*o,L,M,add+addv[o]); if(qR>M) query(2*o+1,M+1,R,add+addv[o]); } }
5.完整代码(模板)
#include <algorithm>#include <cctype>#include <cmath>#include <cstdio>#include <cstdlib>#include <cstring>#include <iomanip>#include <iostream>#include <map>#include <queue>#include <string>#include <set>#include <vector>#include<cmath>#include<bitset>#include<sstream>#include<stack>using namespace std;#define INF 0x7ffffffftypedef long long ll;typedef pair<int,int> P;const int maxnode=1<<17;int n,m;//编号从1到nint _min,_max,_sum;//全局变量,目前位置的最小值、最大值以及累加和int op,qL,qR,v;//m个操作//1 L R v//2 L Rstruct IntervalTree{ int sumv[maxnode],minv[maxnode],maxv[maxnode],addv[maxnode]; //维护信息 //维护结点o,它对应的区间是[L,R] void maintain(int o,int L,int R){ int lc=2*o,rc=2*o+1; sumv[o]=minv[o]=maxv[o]=0; if(R>L){ //考虑左右子树 sumv[o]=sumv[lc]+sumv[rc]; minv[o]=min(minv[lc],minv[rc]); maxv[o]=max(maxv[lc],maxv[rc]); } //考虑add操作 minv[o]+=addv[o]; maxv[o]+=addv[o]; sumv[o]+=addv[o]*(R-L+1); } //修改操作 void update(int o,int L,int R){ int lc=2*o,rc=2*o+1; if(qL<=L&&R<=qR){ //递归边界 addv[o]+=v;//累加边界的add值 } else{ int M=L+(R-L)/2; if(qL<=M) update(lc,L,M); if(qR>M) update(rc,M+1,R); } //递归结束前重新计算本结点的附加信息 maintain(o,L,R); } void query(int o,int L,int R,int add){ if(qL<=L&&R<=qR){ //递归边界:用边界区间的附加信息更新答案 _sum+=sumv[o]+add*(R-L+1); _min=min(_min,minv[o]+add); _max=max(_max,maxv[o]+add); } else{ //不递归统计,累加参数add int M=L+(R-L)/2; if(qL<=M) query(2*o,L,M,add+addv[o]); if(qR>M) query(2*o+1,M+1,R,add+addv[o]); } }};IntervalTree tree;int main(){ while(scanf("%d%d",&n,&m)==2){ memset(&tree,0,sizeof(tree)); while(m--){ scanf("%d%d%d",&op,&qL,&qR); if(op==1){ scanf("%d",&v); tree.update(1,1,n); } else{ _sum=0,_min=INF,_max=-INF; tree.query(1,1,n,0); printf("%d %d %d\n",_sum,_min,_max); } } } return 0;}懂了上述内容,就可以解决poj3468了,这是一道模板题,又是G++超时,C++AChttp://poj.org/problem?id=3468
2.快速序列操作2
1.操作
给定一个有n个元素的数组A[1],A[2],……A[n].任务是设计一个数据结构,支持以下两种操作:
1.set(L,R,v):把A[L],A[L+1],……A[R]全部修改为v(v>=0)
2.query(L,R):计算子序列A[L],A[L+1],……A[R]的元素和、最小值和最大值。
2.代码
#include <algorithm>#include <cctype>#include <cmath>#include <cstdio>#include <cstdlib>#include <cstring>#include <iomanip>#include <iostream>#include <map>#include <queue>#include <string>#include <set>#include <vector>#include<cmath>#include<bitset>#include<sstream>#include<stack>using namespace std;#define INF 0x7ffffffftypedef long long ll;const int maxnode=1<<17;int _sum,_min,_max,op,qL,qR,v;struct IntervalTree{ int sumv[maxnode],maxv[maxnode],minv[maxnode],setv[maxnode]; //维护信息 void maintain(int o,int L,int R){ int lc=2*o,rc=2*o+1; if(R>L){ sumv[o]=sumv[lc]+sumv[rc]; minv[o]=min(minv[lc],minv[rc]); maxv[o]=max(maxv[lc],maxv[rc]); } if(setv[o]>=0){ minv[o]=maxv[o]=setv[o]; sumv[o]=setv[o]*(R-L+1); } } //标记传递 void pushdown(int o){ int lc=2*o,rc=2*o+1; if(setv[o]>=0){ //本结点有标记才传递。注意本题中set值非负 setv[lc]=setv[rc]=setv[o]; setv[o]=-1;//清除本结点标记 } } //更新信息 void update(int o,int L,int R){ int lc=2*o,rc=2*o+1; if(qL<=L&&R<=qR){ //标记修改 setv[o]=v; } else{ pushdown(o); int M=L+(R-L)/2; if(qL<=M) update(lc,L,M); else maintain(lc,L,M); if(qR>M) update(rc,M+1,R); else maintain(rc,M+1,R); } maintain(o,L,R); } void query(int o,int L,int R){ if(setv[o]>=0){ //递归边界1:有set标记 _sum+=setv[o]*(min(R,qR)-max(L,qL)+1); _min=min(_min,setv[o]); _max=max(_max,setv[o]); } else if(qL<=L&&qR>=R){ //递归边界2:边界区间 //此边界区间没有被任何set操作影响 _sum+=sumv[o]; _min=min(_min,minv[o]); _max=max(_max,maxv[o]); } else{ //递归统计 int M=L+(R-L)/2; if(qL<=M) query(2*o,L,M); if(qR>M) query(2*o+1,M+1,R); } }};IntervalTree tree;int main(){ int n,m; while(scanf("%d%d",&n,&m)==2){ memset(&tree,0,sizeof(tree)); memset(tree.setv,-1,sizeof(tree.setv)); tree.setv[1]=0; while(m--){ scanf("%d%d%d",&op,&qL,&qR); if(op==1){ scanf("%d",&v); tree.update(1,1,n); } else{ _sum=0;_min=INF;_max=-INF; tree.query(1,1,n); printf("%d %d %d\n",_sum,_min,_max); } } } return 0;}
0 0
- 线段树模板:点修改,区间修改
- [ 模板 ] 线段树区间修改
- 【模板】区间修改-线段树
- 线段树模板 区间加减 区间修改
- [模板]-线段树-区间修改 + 区间查询
- 模板:线段树(2)区间修改
- 线段树点修改 区间查询
- 线段树总结(点修改+区间修改)
- 【模板】线段树_区间最值、区间求和、修改
- 线段树 区间求和模板 (区间修改)
- 【模板】线段树区间修改、区间求和、查询最值
- 题集+模板:线段树[1]点修改 区间查找 hdu 2795+1394+1754+4302
- 模板:线段树(1)点修改
- 线段树模板(点修改)
- 线段树区间修改
- 线段树区间修改
- 线段树区间修改
- 线段树区间修改和点修改 hdoj 1698(区间修改)、hdoj 1754(点修改)
- 7.6 Git 工具 - 重写历史
- 改版之轮播图
- 设计模式——状态模式详解
- 【Android】仿IOS的滑动按钮
- UI界面设计
- 线段树模板:点修改,区间修改
- Linux启动 图解
- web直接在JSp页面读取服务器的资源
- 声明时间的结构体,输入时间,然后完整的显示出来
- 7.7 Git 工具 - 重置揭密
- 点击用户头像预览大图
- 机器学习基本概念-4
- 数据库的几个概念:主键,外键,索引,唯一索引
- ubuntu terminal use proxy