线段树

线段树

比如说
有一段数
每一个数有一个值
现在有两个操作
把某个区间的所有数加上某个数
输出某个区间的和
例如

Input
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
Output
11
8
20

如果用暴力的话
这一个样例肯定能过
但是数据一大
就T掉
所以我们用到线段树
首先是 1 − n 1-n 1n的区间为根节点
代表 1 − n 1-n 1n的区间的和
然后一分为二
如此下去
得到下图
注意:
树的性质
左儿子的编号是父亲的编号 ∗ 2 *2 2
右儿子的编号是父亲的编号 ∗ 2 + 1 *2+1 2+1

假设我们要在1-5的区间
加上2
有两种方案
一种是从下往上
把1至5的叶子节点加上2

往上加
从1到5,每一次往上加的时间复杂度为 O ( l o g 2 5 + 1 ) O(log_2 5+1) O(log25+1),五次就是 O ( 5 ⋅ ( l o g 2 5 + 1 ) ) O(5·(log_2 5+1)) O(5(log25+1))

然后得到

时间复杂度为 O ( n ⋅ ( l o g 2 n + 1 ) ) O(n·(log_2 n+1)) O(n(log2n+1))
代码实现

void Add()
{
    scanf("%d%d%d",&l,&r,&k);
    for(int i=l;i<=r;++i)
    {
        int Hi=A[i].k;//A[i].k代表第i个数的节点的编号
        while(Hi)
        {
            Tree[Hi].k+=k;
            if(Hi%2)Ti--;//如果是右儿子
            Hi/=2;//回到父亲节点
        }
    }
}

另外一种是从上往下


每个数加上 ( r − l + 1 ) ∗ k (r-l+1)*k (rl+1)k
有一个需要注意
就是比如现在节点的区间为1-3
而现在的 l l l r r r 1 − 1 1-1 11
所以不用分,直接进入左儿子
时间复杂度 O ( l o g 2 n + 1 ) O(log_2n+1) O(log2n+1)
代码实现

//Tree[i].l与Tree[i].r为给节点的区间
//Tree[i].k为给节点的数值
void Add(int Hi,int l,int r,int k)
{
    Tree[Hi].k+=(r-l+1)*k;//加值
    if(l==r && Tree[Hi].l==l && Tree[Hi].r==r)return;//如果已经到了叶子结点就退出
    int Mid=(Tree[Hi].l+Tree[Hi].r)/2;//
    if(l<=r && r<=Mid)Add(Hi*2,l,r,k);//直接进入左儿子
    else if(l>Mid && l<=r)Add(Hi*2+1,l,r,k);//进入右儿子
    else
    {
        if(l<=Mid)Add(Hi*2,l,Mid,k);//
        if(r>Mid)Add(Hi*2+1,Mid+1,r,k);//分开
    }
}

那如何求值呢?
其实与加值差不多
只要当前的 l l l r r r与此节点的区间的范围相等
就直接 r e t u r n return return本节点的值
其他都差不多

int Num(int Hi,int l,int r)
{
    if(Tree[Hi].l==l && Tree[Hi].r==r)
        return Tree[Hi].k;
    int Mid=(Tree[Hi].l+Tree[Hi].r)/2,Ans=0;
    if(l<=r && r<=Mid)Ans=Num(Hi*2,l,r); 
    else if(l>Mid && l<=r)Ans=Num(Hi*2+1,l,r);
    else
    {
        if(l<=Mid)Ans+=Num(Hi*2,l,Mid);
        if(r>Mid)Ans+=Num(Hi*2+1,Mid+1,r);
    }
    return Ans;
}

还有建树

void Tree_Buil(int Hi,int l,int r)
{
    Tree[Hi].l=l;Tree[Hi].r=r;
    if(l==r)Tree[Hi].k=A[l];
    else
    {
        int Mid=(l+r)/2;
        Tree_Buil(Hi*2,l,Mid);
        Tree_Buil(Hi*2+1,Mid+1,r);
        Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;//父亲节点等于两个儿子加起来
    }
    return;
}

然后放在一起
(我加了快读)
(下面还有!)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
struct tree_1
{
    ll l,r,k;
}Tree[400250];
int A[100250];
int Operation,Sum;
int n,m,k;
int Read()
{
    char c=getchar();int l=0,r=1;
    while(c!='-' && c<'0' && 'c'>'9')c=getchar();
    if(c=='-')r=-1;
    while(c>='0' && c<='9'){l=l*10+c-'0',c=getchar();}
    return l*r;
}
void Tree_Buil(int Hi,int l,int r)
{
    Tree[Hi].l=l;Tree[Hi].r=r;
    if(l==r)Tree[Hi].k=A[l];
    else
    {
        int Mid=(l+r)/2;
        Tree_Buil(Hi*2,l,Mid);
        Tree_Buil(Hi*2+1,Mid+1,r);
        Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;
    }
    Sum++;
    return;
}
//--------------------------------------------------------------
void Add(int Hi,int l,int r,int k)
{
    Tree[Hi].k+=(r-l+1)*k;
    if(l==r && Tree[Hi].l==l && Tree[Hi].r==r)return;
    int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
    if(l<=r && r<=Mid)Add(Hi*2,l,r,k);
    else if(l>Mid && l<=r)Add(Hi*2+1,l,r,k);
    else
    {
        if(l<=Mid)Add(Hi*2,l,Mid,k);
        if(r>Mid)Add(Hi*2+1,Mid+1,r,k);
    }
}
//--------------------------------------------------------------
int Num(int Hi,int l,int r)
{
    if(Tree[Hi].l==l && Tree[Hi].r==r)
        return Tree[Hi].k;
    int Mid=(Tree[Hi].l+Tree[Hi].r)/2,Ans=0;
    if(l<=r && r<=Mid)Ans=Num(Hi*2,l,r); 
    else if(l>Mid && l<=r)Ans=Num(Hi*2+1,l,r);
    else
    {
        if(l<=Mid)Ans+=Num(Hi*2,l,Mid);
        if(r>Mid)Ans+=Num(Hi*2+1,Mid+1,r);
    }
    return Ans;
}
//--------------------------------------------------------------
void Write()
{
    int l,r;
    scanf("%d%d",&l,&r);
    printf("%d\n",Num(1,l,r)); 
    return;
}
//--------------------------------------------------------------
int main()
{
    n=Read();m=Read();
    for(int i=1;i<=n;++i)A[i]=Read();
    Tree_Buil(1,1,n);
    for(int i=1;i<=m;++i)
    {
        int l,r;
        scanf("%d",&Operation);
        if(Operation==1)
        {
            scanf("%d%d%d",&l,&r,&k);
            Add(1,l,r,k);
        }
        if(Operation==2)Write();
    }
    return 0;
}

But

上面的程序会超时
所以我们要优化
加一个 s t o r e store store操作
来记录我们加的数据
就不用算到底
避免不必要的计算
当然
这只是针对 A d d Add Add(加)操作从上往下的方法
当我们需要加某一个数时
我们递归到当前 l l l r r r
与当前节点的区间 l l l r r r相等时
我们就直接把 k k k加到 s t o r e store store并退出
下次需要此节点的时候
就把 s t o r e store store往下延伸到儿子去

void Go(int Hi,int Len)
{
    if(!Tree[Hi].store)return;//如果是0的活就没有,可以直接退出
    Tree[Hi*2].store+=Tree[Hi].store;
    Tree[Hi*2+1].store+=Tree[Hi].store;//加到儿子节点
    Tree[Hi*2].k+=Tree[Hi].store*(Len-Len/2);
    Tree[Hi*2+1].k+=Tree[Hi].store*(Len/2);//儿子的数值也要加
    Tree[Hi].store=0;//清零
}

A d d Add Add

void Add(int Hi,int l,int r,int k)
{
    if(Tree[Hi].l==l && Tree[Hi].r==r)
    {
        Tree[Hi].k+=(r-l+1)*k;
        Tree[Hi].store+=k;//加到store
        return;
    }
    if(Tree[Hi].l==Tree[Hi].r)return;
    Go(Hi,Tree[Hi].r-Tree[Hi].l+1);//往下延伸
    int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
    if(l<=r && r<=Mid)Add(Hi*2,l,r,k);
    else if(l>Mid && l<=r)Add(Hi*2+1,l,r,k);
    else
    {
        if(l<=Mid)Add(Hi*2,l,Mid,k);
        if(r>Mid)Add(Hi*2+1,Mid+1,r,k);
    }
    Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;
}

W r i t e Write Write

long long Num(int Hi,int l,int r)
{
    if(Tree[Hi].l==l && Tree[Hi].r==r)
        return Tree[Hi].k;
    Go(Hi,Tree[Hi].r-Tree[Hi].l+1);//往下延伸
    int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
    ll Ans=0;
    if(l<=r && r<=Mid)Ans+=Num(Hi*2,l,r); 
    else if(l>Mid && l<=r)Ans+=Num(Hi*2+1,l,r);
    else
    {
        if(l<=Mid)Ans+=Num(Hi*2,l,Mid);
        if(r>Mid)Ans+=Num(Hi*2+1,Mid+1,r);
    }
    return Ans;
}

最后是总的代码
(下面还有!!!!)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
struct tree_1
{
    ll l,r,k,store;
}Tree[400250];
int A[100250];
int Operation,Sum;
int n,m,k;
int Read()
{
    char c=getchar();int l=0,r=1;
    while(c!='-' && c<'0' && 'c'>'9')c=getchar();
    if(c=='-')r=-1;
    while(c>='0' && c<='9'){l=l*10+c-'0',c=getchar();}
    return l*r;
}
//--------------------------------------------------------------
void Go(int Hi,int Len)
{
    if(!Tree[Hi].store)return;
    Tree[Hi*2].store+=Tree[Hi].store;
    Tree[Hi*2+1].store+=Tree[Hi].store;
    Tree[Hi*2].k+=Tree[Hi].store*(Len-Len/2);
    Tree[Hi*2+1].k+=Tree[Hi].store*(Len/2);
    Tree[Hi].store=0;
}
void Tree_Buil(int Hi,int l,int r)
{
    Tree[Hi].l=l;Tree[Hi].r=r;
    if(l==r)Tree[Hi].k=A[l];
    else
    {
        int Mid=(l+r)/2;
        Tree_Buil(Hi*2,l,Mid);
        Tree_Buil(Hi*2+1,Mid+1,r);
        Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;
    }
    return;
}
//--------------------------------------------------------------
void Add(int Hi,int l,int r,int k)
{
    if(Tree[Hi].l==l && Tree[Hi].r==r)
    {
        Tree[Hi].k+=(r-l+1)*k;
        Tree[Hi].store+=k;
        return;
    }
    if(Tree[Hi].l==Tree[Hi].r)return;
    Go(Hi,Tree[Hi].r-Tree[Hi].l+1);
    int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
    if(l<=r && r<=Mid)Add(Hi*2,l,r,k);
    else if(l>Mid && l<=r)Add(Hi*2+1,l,r,k);
    else
    {
        if(l<=Mid)Add(Hi*2,l,Mid,k);
        if(r>Mid)Add(Hi*2+1,Mid+1,r,k);
    }
    Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;//加上之前没加的
}
//--------------------------------------------------------------
ll Num(int Hi,int l,int r)
{
    if(Tree[Hi].l==l && Tree[Hi].r==r)
        return Tree[Hi].k;
    Go(Hi,Tree[Hi].r-Tree[Hi].l+1);
    int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
    ll Ans=0;
    if(l<=r && r<=Mid)Ans+=Num(Hi*2,l,r); 
    else if(l>Mid && l<=r)Ans+=Num(Hi*2+1,l,r);
    else
    {
        if(l<=Mid)Ans+=Num(Hi*2,l,Mid);
        if(r>Mid)Ans+=Num(Hi*2+1,Mid+1,r);
    }
    return Ans;
}
//--------------------------------------------------------------
int main()
{
    Sum=0;
    n=Read();m=Read();
    for(int i=1;i<=n;++i)A[i]=Read();
    Tree_Buil(1,1,n);
    for(int i=1;i<=m;++i)
    {
        int l,r;
        Operation=Read();
        if(Operation==1)
        {
            l=Read(),r=Read(),k=Read();
            Add(1,l,r,k);
        }
        if(Operation==2)
        {
            l=Read(),r=Read();
            printf("%lld\n",Num(1,l,r));
        }
    }
    return 0;
}

最后献上几张图
(两天啊!!!!!)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值