线段树模板:点修改,区间修改

来源:互联网 发布: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
原创粉丝点击