• 线段树


    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
    ", 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
    ", 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
    ", rquery(l, r, root));
        }
        return 0;
    }
    
  • 相关阅读:
    国庆·生日
    国足
    Eason's concert
    今天的斩获
    The 4400
    闷热
    24
    一直登录不了。。。原来是因为。。。
    黄色暴雨警告
    绝密飞行
  • 原文地址:https://www.cnblogs.com/lfyzoi/p/10648063.html
Copyright © 2020-2023  润新知