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

最近在看《算法竞赛入门经典训练指南》, 感觉以前的想法几乎完全是错的,模板并不一定能直接套。

最近在看线段树,才知道线段树每个节点的附加信息才是重头戏,因此完全套模板是不可行的。但是思想方法可以借鉴,并在此基础上 加以改进。

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include
#include
#include
using namespace std;
#define INF 0x7fffffff
#define maxn 100005
#define maxnode 200005

//线段树的节点编号是1-n
int 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

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include
#include
#include
#include
using namespace std;
#define INF 0x7fffffff
typedef long long ll;
typedef pair P;
const int maxnode=1<<17;
int n,m;
//编号从1到n
int _min,_max,_sum;//全局变量,目前位置的最小值、最大值以及累加和
int op,qL,qR,v;
//m个操作
//1 L R v
//2 L R
struct 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++AC http://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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include
#include
#include
#include
using namespace std;
#define INF 0x7fffffff
typedef 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;
}




你可能感兴趣的:(线段树,算法竞赛入门经典,算法,数据结构)