洛谷 线段树 1 模板

题目大意:

已知一个数列,你需要进行下面两种操作:
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往下传。这个优化可以成功卡过这道题(不开快读),我不仅了优化还加了快读,于是我就成功卡过这道题

Accepted Code & Notes:

#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

你可能感兴趣的:(线段树)