线段树

4N空间版

#include<iostream>
#include<cstdio>
using namespace std; 
const int SIZE=100010;
int N, M;

inline long long read()								//快读可以定义为内联函数,效率更高 
{
    long long s=0, w=1;
	char ch=getchar();
    while(ch<'0'  || ch>'9' ){ if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0' && ch<='9'){ s=s*10+ch-'0';	 ch=getchar(); }
    return s*w;
}

struct SegNode
{
	int l, r;										//[l, r]为当前结点表示的区间,bj为懒人标记 
	long long val, bj;								//根据题目要求,一定注意数据范围! 
}ST[SIZE<<2];										//使用完全二叉树的固定位置法,空间要开到N的4倍 

void buildT(int x, int L, int R)					//先序遍历构建线段树,叶子处读入数据(数据区间为[L, R])
{
	ST[x].l=L, ST[x].r=R, ST[x].bj=0;				//第一次调用buildT时,x为根结点,通常为1 
	if(L==R)										//已到达叶子结点 
	{
		ST[x].val=read();							//读入数值 
		//ST[x].val=arr[L];							//这种方式以一个数组初始化线段树 
		return ;
	}
	int mid=(L+R)>>1;
	buildT(x<<1, L, mid);							//递归构建左、右子树 
	buildT((x<<1)+1, mid+1, R);						//注意"+"的优先级高于"<<"的优先级,搞不清的话写x*2+1更安全 
	ST[x].val=ST[x<<1].val+ST[(x<<1)+1].val;		//回溯时合并左右子树的值 
}

													//x有bj的含义:当前结点的val是正确的,但是bj还没有下传
													//pushDown(x)的目的:让x左、右孩子结点的val正确,同时设置好其bj,清除x的bj 
void pushDown(int x)								//当前结点的懒人标记下传 
{
	if(ST[x].bj && ST[x].l!=ST[x].r)				//有标记且x不是叶子结点,可以下传,从叶子结点下传可能会RE 
	{
		long long k=ST[x].bj;
		ST[x<<1].bj+=k;
		ST[x<<1].val+=k*(ST[x<<1].r-ST[x<<1].l+1);	//这里非常容易错,要乘k 
		ST[(x<<1)+1].bj+=k;
		ST[(x<<1)+1].val+=k*(ST[(x<<1)+1].r-ST[(x<<1)+1].l+1);	//这里非常容易错,要乘k
		ST[x].bj=0;									//清除x结点标记 
	}
}

void radd(int L, int R, int k, int x=1)				//区间加,[L, R] + k,x为根 
{
	if(L<=ST[x].l && ST[x].r<=R)					//当前结点表示的区间被[L, R]覆盖,可直接被修改、设置懒人标记,停止递归 
	{
		ST[x].val+=k*(ST[x].r-ST[x].l+1);			//修改区间val至正确值 
		ST[x].bj+=k;								//设懒人标记 
		return;
	}
	int m=(ST[x].l+ST[x].r)>>1;
	pushDown(x);									//要递归修改儿子,先把标记下传 
	if(L<=m)	radd(L, R, k, x<<1);				//递归修改左孩子 
	if(R>=m+1)	radd(L, R, k, (x<<1)+1);			//递归修改右孩子 
	ST[x].val=ST[x<<1].val+ST[(x<<1)+1].val; 		//回溯时合并,更新父结点的值 
}
 
long long rquery(int L, int R, int x=1)				//区间和[L, R]查询,x为根 
{
	if(L<=ST[x].l && ST[x].r<=R)					//当前结点表示的区间被[L, R]覆盖,可直接返回值 
		return ST[x].val;
	int m=(ST[x].l+ST[x].r)>>1;
	pushDown(x);									//要向孩子查询,先更新孩子的值 
	long long ans=0;
	if(L<=m) 	ans+=rquery(L, R, x<<1);			//加左子树返回的值 
	if(R>=m+1)  ans+=rquery(L, R, (x<<1)+1);		//加右子树返回的值 
	return ans;
}

int main()
{
	N=read(), M=read();
    buildT(1, 1, N);
    for(register int i=1, c, l, r, k; i<=M; i++)	//频繁使用的循环遍历设置为寄存器类型(register)会带来一定效率提升 
    {
        c=read(), l=read(), r=read();
        if(c==1)	k=read(), radd(l, r, k);
        if(c==2)	printf("%lld\n", rquery(l, r));
    }
    return 0;
}

2N空间版(使用指针,也可以预分配空间,使用数组下标作为“指针”连接结点)

/*使用指针存储线段树,空间可以控制到2N,也可以开一个2N的数组,使用“逻辑指针”连接。 
*/ 
#include<iostream>
#include<cstdio>
using namespace std; 
const int SIZE=100010;
int N, M;

inline long long read()								//快读可以定义为内联函数,效率更高 
{
    long long s=0, w=1;
	char ch=getchar();
    while(ch<'0'  || ch>'9' ){ if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0' && ch<='9'){ s=s*10+ch-'0';	 ch=getchar(); }
    return s*w;
}

struct SegNode
{
	int l, r;										//[l, r]为当前结点表示的区间,bj为懒人标记 
	long long val, bj;								//根据题目要求,一定注意数据范围! 
	SegNode *lc, *rc;								//指向左、右子树的指针
	SegNode(int left, int right){ l=left, r=right, bj=0, lc=rc=NULL; } 
}; 

void buildT(SegNode * &x, int L, int R)				//x必须定义为引用,否则递归时无法修改指针的值
{
	x=new SegNode(L, R);							//新建结点 
	if(L==R)										//已到达叶子结点 
	{
		x->val=read();
		//x->val=arr[L];							//这种方式以一个数组初始化线段树 
		return ;
	}
	int mid=(L+R)>>1;
	buildT(x->lc, L, mid);							//递归构建左、右子树 
	buildT(x->rc, mid+1, R); 
	x->val=x->lc->val+x->rc->val;					//回溯时合并左右子树的值 
}

													//x有bj的含义:当前结点的val是正确的,但是bj还没有下传
													//pushDown(x)的目的:让x左、右孩子结点的val正确,同时设置好其bj,清除x的bj 
void pushDown(SegNode *x)							//当前结点的懒人标记下传 
{
	if(x->bj && x->l!=x->r)							//有标记且x不是叶子结点,可以下传,从叶子结点下传可能会RE 
	{
		long long k=x->bj;
		x->lc->bj+=k;
		x->lc->val+=k*(x->lc->r-x->lc->l+1);		//这里非常容易错,要乘k 
		x->rc->bj+=k;
		x->rc->val+=k*(x->rc->r-x->rc->l+1);		//这里非常容易错,要乘k
		x->bj=0;									//清除x结点标记 
	}
}

void radd(int L, int R, int k, SegNode *x)			//区间加,[L, R] + k,x为根 
{
	if(L<=x->l && x->r<=R)							//当前结点表示的区间被[L, R]覆盖,可直接被修改、设置懒人标记,停止递归 
	{
		x->val+=k*(x->r-x->l+1);					//修改区间val至正确值 
		x->bj+=k;									//设懒人标记 
		return;
	}
	int m=(x->l+x->r)>>1;
	pushDown(x);									//要递归修改儿子,先把标记下传 
	if(L<=m)	radd(L, R, k, x->lc);				//递归修改左孩子 
	if(R>=m+1)	radd(L, R, k, x->rc);				//递归修改右孩子 
	x->val=x->lc->val+x->rc->val;			 		//回溯时合并,更新父结点的值 
}
 
long long rquery(int L, int R, SegNode *x)			//区间和[L, R]查询,x为根 
{
	if(L<=x->l && x->r<=R)							//当前结点表示的区间被[L, R]覆盖,可直接返回值 
		return x->val;
	int m=(x->l+x->r)>>1;
	pushDown(x);									//要向孩子查询,先更新孩子的值 
	long long ans=0;
	if(L<=m) 	ans+=rquery(L, R, x->lc);			//加左子树返回的值 
	if(R>=m+1)  ans+=rquery(L, R, x->rc);			//加右子树返回的值 
	return ans;
}

int main()
{
	SegNode *root=NULL;								//定义一个根指针为空 
	N=read(), M=read();
    buildT(root, 1, N);
    for(register int i=1, c, l, r, k; i<=M; i++)	//频繁使用的循环遍历设置为寄存器类型(register)会带来一定效率提升 
    {
        c=read(), l=read(), r=read();
        if(c==1)	k=read(), radd(l, r, k, root);
        if(c==2)	printf("%lld\n", rquery(l, r, root));
    }
    return 0;
}

二维的线段树(加、乘两种操作),重点在于两种操作的处理顺序,请自行查阅资料。为了方便看清PushDown里面计算两个bj的过程,我加了一堆子引用重写了一下,看起来更简洁,在编码中也可以使用这种方法。

void pushDown(SegNode *x)
{
    if((x->bjj || x->bjc!=1) && x->l!=x->r)         //有标记且x不是叶子结点,可以下传,从叶子结点下传可能会RE
    {                                               //仔细想一想,pushDown的时候,x不可能是叶子节点的~~~
        long long &xc=x->bjc, &xj=x->bjj;
        long long &lv=x->lc->v, &lc=x->lc->bjc, &lj=x->lc->bjj;
        long long &rv=x->rc->v, &rc=x->rc->bjc, &rj=x->rc->bjj;
        long long lsize=x->lc->r-x->lc->l+1, rsize=x->rc->r-x->rc->l+1;
        lv=(lv*xc%P+xj*lsize%P)%P;
        rv=(rv*xc%P+xj*rsize%P)%P;
        lc=xc*lc%P;
        lj=(lj*xc%P+xj)%P;
        rc=xc*rc%P;
        rj=(rj*xc%P+xj)%P; 
        xc=1;
        xj=0;
    }
}
#include<iostream>
#include<cstdio>
using namespace std; 
const int SIZE=100010;
int N, M, P;                                        //P为模

inline long long read()
{
    long long s=0, w=1;
    char ch=getchar();
    while(ch<'0'  || ch>'9' ){ if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0' && ch<='9'){ s=s*10+ch-'0';    ch=getchar(); }
    return s*w;
}

struct SegNode
{
    long long l, r, v, bjj, bjc;                    //为了避免出错,尽量全部使用long long
    SegNode *lc, *rc;                               //懒人标记bjc要初始化为1
    SegNode(int left, int right){ l=left, r=right, bjj=0, bjc=1, lc=rc=NULL; }
};

void buildT(SegNode *&x, int L, int R)              //x必须定义为引用,否则递归时无法修改指针的值
{
    x=new SegNode(L, R);
    if(L==R)
    {
        x->v=read(); 
        //x->v=arr[L]                               //这种方式以一个数组初始化线段树 
        return ;
    }
    int mid=(L+R)>>1;
    buildT(x->lc, L, mid);
    buildT(x->rc, mid+1, R);
    x->v=(x->lc->v+x->rc->v)%P;                     //为避免溢出,在计算的时候随时取模
}

void pushDown(SegNode *x)
{
    if((x->bjj || x->bjc!=1) && x->l!=x->r)         //有标记且x不是叶子结点,可以下传,从叶子结点下传可能会RE
    {                                               //仔细想一想,pushDown的时候,x不可能是叶子节点的~~~
        x->lc->v=(x->lc->v*x->bjc%P+x->bjj*(x->lc->r-x->lc->l+1)%P)%P;
        x->rc->v=(x->rc->v*x->bjc%P+x->bjj*(x->rc->r-x->rc->l+1)%P)%P;
        x->lc->bjc=(x->bjc*x->lc->bjc)%P;
        x->lc->bjj=(x->lc->bjj*x->bjc%P+x->bjj)%P;
        x->rc->bjc=(x->bjc*x->rc->bjc)%P;
        x->rc->bjj=(x->rc->bjj*x->bjc%P+x->bjj)%P; 
        x->bjc=1;
        x->bjj=0;
    }
}

void radd(int L, int R, int k, int c, SegNode *x)
{
    if(L<=x->l && x->r<=R)
    {
        if(c==2)
        {
            x->v=(x->v+k*(x->r-x->l+1)%P)%P;
            x->bjj=(x->bjj+k)%P;
        }
        if(c==1)
        {
            x->v=(x->v*k)%P;
            x->bjc=(x->bjc*k)%P;
            x->bjj=(x->bjj*k)%P;                    //待向下传递的加的那部分也要乘
        }
        x->v%=P;
        return;
    }
    int m=(x->l+x->r)>>1;
    pushDown(x); 
    if(L<=m)        radd(L, R, k, c, x->lc);
    if(R>=m+1)      radd(L, R, k, c, x->rc);
    x->v=(x->lc->v+x->rc->v)%P;
}

long long rquery(int L, int R, SegNode *x) 
{
    if(L<=x->l && x->r<=R)  return x->v%P;
    int m=(x->l+x->r)>>1;
    pushDown(x);
    long long ans=0;
    if(L<=m)    ans+=rquery(L, R, x->lc), ans%=P;
    if(R>=m+1)  ans+=rquery(L, R, x->rc), ans%=P;
    return ans%P;
}

int main()
{
    SegNode *root;
    N=read(), M=read(), P=read();
    buildT(root, 1, N);
    for(register int i=1, c, l, r, k; i<=M; i++)
    {
        c=read(), l=read(), r=read();
        if(c<=2)    k=read(), radd(l, r, k, c, root);
        if(c==3)    printf("%lld\n", rquery(l, r, root));
    }
    return 0;
}
posted @ 2019-04-03 12:27  LFYZOI题解  阅读(277)  评论(0编辑  收藏  举报