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;
}