已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.求出某区间每一个数的和并且输出
这道题有点恶心,我觉得线段树的题都恶心我打了两个小时,这还只是一道模板。后来我看了一看洛谷题解,发现一堆其它解法,赞最多的那一篇线段树的题解写的很好,不过代码有点恶心,个人觉得线段树的代码都恶心于是到我了
然后讲一下线段树吧
主要操作:
1.建树
就针对这道题的一个build:
void build(int now,int l,int r) {
t[now].l=l;t[now].r=r;
if (l==r) {read(t[now].d); return;}
int mid=(l+r)>>1;
build(ls,l,mid);build(rs,mid+1,r);
t[now].d=t[ls].d+t[rs].d;
}
2.修改区间(点)
这道题时修改区间,于是代码Add就是修改区间的:
void Add(int now,int l,int r,ll dat) {
if (t[now].l==l&&t[now].r==r) {
t[now].d+=dat*(t[now].r-t[now].l+1);
t[now].lazy+=dat;
return;
}
downdata(now);
int mid=(t[now].l+t[now].r)>>1;
if (r<=mid) Add(ls,l,r,dat);
else if(l>mid) Add(rs,l,r,dat);
else {
Add(ls,l,mid,dat);
Add(rs,mid+1,r,dat);
}
t[now].d=t[ls].d+t[rs].d;
}
3.查询 还是针对这道题的一个Ask:
ll Ask(int now,int l,int r) {
if (t[now].l==l&&t[now].r==r) return t[now].d;
downdata(now);
int mid=(t[now].l+t[now].r)>>1;
if (r<=mid) return Ask(ls,l,r);
else if (l>mid) return Ask(rs,l,r);
else return Ask(ls,l,mid)+Ask(rs,mid+1,r);
}
downdata在下面
我们如果每次进行(1)修改操作都下传就注定T
反正我之前没用lazy下传的时候就T了3个点
lazy操作如下:
void downdata(int now)
{
if(t[now].lazy)
{
t[ls].lazy+=t[now].lazy;
t[ls].d+=t[now].lazy*(t[ls].r-t[ls].l+1);
t[rs].lazy+=t[now].lazy;
t[rs].d+=t[now].lazy*(t[rs].r-t[rs].l+1);
t[now].lazy=0;
}
}
我们用这个downdata一次把所有记录下来的lazy往下传。这个优化可以成功卡过这道题(不开快读),我不仅了优化还加了快读,于是我就成功卡过这道题
#include
#include
#define ls (now*2)
#define rs (now*2+1)
#define ll long long
using namespace std;
const int N=1e5+5;
struct Tree {
int l,r;
ll d,lazy;
}t[N<<2];
ll n,m;
inline void read(ll &f) {//输入流
char c=getchar();f=0;int ag=1;
while(!isdigit(c)) {if (c=='-') ag=-1; c=getchar();}
while(isdigit(c)){f=(f<<3)+(f<<1)+c-48;c=getchar();}
f*=ag; return;
}
void write(ll x) {//输出流
if(x>9) write(x/10);putchar(x%10+48);return;
}
void writeln(ll x) {//输出流,这个是copy之前程序的
if (x<0) putchar('-'),x*=-1; write(x); putchar('\n'); return;
}
void build(int now,int l,int r) {
t[now].l=l;t[now].r=r;//记录now这个点的区间
if (l==r) {read(t[now].d); return;}
//如果是叶子节点就赋值给这个点
int mid=(l+r)>>1;
build(ls,l,mid);build(rs,mid+1,r);
//往下分
t[now].d=t[ls].d+t[rs].d;
//当前点的值是左孩子+右孩子的值
//至于ls&rs看define
}
void downdata(int now)//lazy就是当前点"积累"下来的所有dat
//dat详见下面的代码
{
if(t[now].lazy)
{
t[ls].lazy+=t[now].lazy;//
t[ls].d+=t[now].lazy*(t[ls].r-t[ls].l+1);
t[rs].lazy+=t[now].lazy;
t[rs].d+=t[now].lazy*(t[rs].r-t[rs].l+1);
//左右儿子操作都差不多
//这个自己理解
t[now].lazy=0;
}
}
void Add(int now,int l,int r,ll dat) {
if (t[now].l==l&&t[now].r==r) {//找到了要找的区间
t[now].d+=dat*(t[now].r-t[now].l+1);
t[now].lazy+=dat;//dat就是要加的值
return;
}
downdata(now);//下传
int mid=(t[now].l+t[now].r)>>1;
if (r<=mid) Add(ls,l,r,dat);//区间在左孩子
else if(l>mid) Add(rs,l,r,dat);//区间在右孩子
else {
Add(ls,l,mid,dat);
Add(rs,mid+1,r,dat);//两边都有一部分
}
t[now].d=t[ls].d+t[rs].d;//计算左右孩子的点权和
}
ll Ask(int now,int l,int r) {
if (t[now].l==l&&t[now].r==r) return t[now].d;
//找到了查询的区间返回当前区间所有数的和
downdata(now);//下传
int mid=(t[now].l+t[now].r)>>1;
if (r<=mid) return Ask(ls,l,r);
else if (l>mid) return Ask(rs,l,r);
else return Ask(ls,l,mid)+Ask(rs,mid+1,r);
//同Add的操作
}
int main() {
read(n);read(m);
build(1,1,n);
for (int i=1;i<=m;i++) {
ll flag,x,y;
read(flag);read(x);read(y);
if (flag==1) {
ll dat;
read(dat);
Add(1,x,y,dat);
} else writeln(Ask(1,x,y));
}//这一段不用说了吧
}
线段树在于细节,细节做好了你的树就“种”好了
看看我,一个小细节T了3个点,改这道题改了1.5h+敲了15min=1.75h