<span>线段树和树状数组</span>

线段树

引入1:有n个数(n<=50000)个数,m(m<=50000)次询问。每次询问区间L到R的数的和。要求输出每一次询问的结果......
分析:
1.用前缀和问题进行求解:再开一个数组(暂且记为b[n],设n个数所组成的数组为a[n]),b[i]用来记录从a[1]到a[i]的所有数字的和(即 b[1]=a[1],b[2]=b[1]+a[2],...,b[i]=b[i-1]+a[i])。这样对于m次询问,只需要对数组b进行相应的操作就可以了。
eg:求数组a中区间[L,R]的所有数字的和sum,即sum=a[L]+a[L+1]+...+a[R-1]+a[R]。由于我们之前已经对数组a求了前缀和,并得到前缀和数组b[n]。因此对b[n]进行操作就可以了,即sum=b[R]-b[L-1]。
2.用线段树进行求解:这里先告诉大家可以用线段树来进行求解,具体如何求解我们可以等对线段树进行一定的讲解后再来进行求解。
引入2:RMQ(Range Minimum/Maximum Query)问题
有n(n<=50000)个数,m(m<=50000)次询问,要求每次输出区间[L,R]的数的最大值。
分析:
1.用ST表进行求解:一种利用dp思想求解区间最值的倍增算法。
定义:f(i,j)表示[i,i+\(2^j\)−1]这段长度为2^j的区间中的最大值。
预处理:f(i,0)=a[i]。即[i,i]区间的最大值就是a[i]。
状态转移:将[i,j]平均分成两段,一段为[i,i+2^(j−1)−1],另一段为[i+2^(j−1),i+2^j−1]。两段的长度均为2^(j−1)。f(i,j)的最大值即这两段的最大值中的最大值。得到f(i,j)=max{f(i,j−1),f(i+2^(j−1),j−1)}。
查询:若需要查询的区间为[i,j],则需要找到两个覆盖这个闭区间的最小幂区间。
这两个区间可以重复,因为两个区间是否相交对区间最值没有影响。(如下图)

当然求区间和就肯定不行了。这也就是RMQ的限制性。
因为区间的长度为j−i+1,所以可以取k=\(\log_2 (j-i+1)\)
则所求即为max{f(i,k),f(j−2k+1,k)}。
参考代码如下:

#include<iostream>//这段代码以询问最大值为例
#include<algorithm>
#include<cmath>
using namespace std;
int a[50005];
int f[50005][16];
int main()
{
    ios::sync_with_stdio(false);
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    cin>>a[i];
    for(int i=1;i<=n;i++)
    f[i][0]=a[i];//可以参照上文的预处理部分
    /*
    注意外部循环从j开始,
    因为初始状态为f[i][0],
    以i为外层会有一些状态遍历不到。
    */ 
    for(int j=1;j<=16;j++)//2^16>50000足够了
       for(int i=1;i<=n;i++)//可以参照上文的状态转移部分
          if(i+(1<<j)-1<=n)//判断区间最右端的值是否超过n
          {
              f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
          }
    int l,r;
    while(m--)//处理m次询问
    {
        cin>>l>>r;
        int k=(int)(log((double)(r-l+1))/(log(2.0)));
        cout<<max(f[l][k],f[r-(1<<k)+1][k])<<endl;
    }
    return 0;
}

参考例题:
【模板】ST表
质量检测
2.用线段树进行求解:这里先告诉大家可以用线段树来进行求解,具体如何求解我们可以等对线段树进行一定的讲解后再来进行求解。
概念:
线段树是用一种树状结构来存储一个连续区间的信息的数据结构。它主要用于处理一段连续区间的插入,查找,统计,查询等操作。
复杂度:设区间长度是n,所有操作的复杂度是logn级别。

  • 线段树也是一种树形结构,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶节点。线段树是建立在线段的基础上,每个节点都代表了一条线段[a,b],长度为 1 的线段称为元线段。非元线段都有两个子节点,左节点代表的线段为[a,(a+b)/2],右节点代表的线段为[((a+b)/2)+1,b]。
  • 线段[1,7]的线段树和[2,5]的分解

    线段树的几点性质:
  • 线段树是平衡的二叉树,最大深度logn(n为线段树所表示区间的长度)
  • 树中的每一个节点代表对应一个区间(叶子节点是一个点……)
  • 每个节点(所代表的区间)完全包含它的所有孙节点
  • 对于任意两个节点(所代表的区间):要么完全包含,要么互不相交
  • 在进行区间操作和统计时把区间等价转化成若干个子区间(logn个)的相同操作
  • 任意的线段[a,b]在线段树的查询或查找过程中把这个线段最多分成log(b-a)份(显然每一层最多两个区间)
  • So,线段树除建树外的操作都是log(n)级别的复杂度。
    使用线段树可以快速查找某个节点在若干条区间中出现的次数、某个区间的最大或最小值、某个区间的和等问题。这时就需要了解线段树的 3 个基本操作:建树、更新和查询。

例1:
给你一个长度为n的序列,有如下操作,将第i个数加或减x,求区间Li到Ri的和。
解析:
1:建树
-建树时,可以使用结构体的方法,将节点的左右端点、记录的信息(最大值、最小值、区间和等)存储进去;也可使用一个数组保存,使用数组时,省略了左右端点,因为在递归过程中,左右端点可以通过值传递的方式得到。建树就是一个遍历二叉树的过程,从根节点出发,依次遍历左子树,到元线段返回,再遍历右子树,直到遍历结束。
建树过程代码如下:

void buildTree(int p, int l,int r)//p:数组下标;l:区间左端点;r:区间右端点
{
    if(l==r)  
    {
        tree[p]=arr[l];
        return;
    }
    int mid=(l+r)/2;
    buildTree(p*2,l,mid);//建立左子树
    buildTree(p*2+1,mid+1,r);//建立右子树
    tree[p]=tree[p*2]+tree[p*2+1];
}

-更新操作和查询操作类似,时间复杂度都为O(logn)。当执行更新操作时,要遍历到叶子节点,再层层递归更新,而查询操作只需要查询到包括的区间为止。
更新操作如下:

void change(int p,int l, int r,int x, int num)//给下标为x的位置加上num
{
    if(l==r)
    {
        tree[p]+=num;
        return;
    }
    int mid=(l+r)/2;
    if(x<=mid)
    {
        change(p*2,l,mid,x,num);
    }
    else
    {
        change(p*2+1,mid+1,r,x,num);
    }
    tree[p]=tree[p*2]+tree[p*2+1];
}

查询操作如下:

int query(int p,int l,int r, int x, int y)
{
    if(x<=l&&r<=y)
    return tree[p];
    int mid=(l+r)/2;
    if(y<=mid)
    return query(p*2,l,mid,x,y);
    if(x>mid)
    return query(p*2+1,mid+1,r,x,y);
    return (query(p*2,l,mid,x,y)+query(p*2+1,mid+1,r,x,y));
}

完整代码如下:

#include<iostream>
using namespace std;
int arr[10005],tree[10005];
void buildTree(int p, int l,int r)//p:数组下标;l:区间左端点;r:区间右端点
{
    if(l==r)  
    {
        tree[p]=arr[l];
        return;
    }
    int mid=(l+r)/2;
    buildTree(p*2,l,mid);//建立左子树
    buildTree(p*2+1,mid+1,r);//建立右子树
    tree[p]=tree[p*2]+tree[p*2+1];
}
void change(int p,int l, int r,int x, int num)//给下标为x的位置加上num
{
    if(l==r)
    {
        tree[p]+=num;
        return;
    }
    int mid=(l+r)/2;
    if(x<=mid)
    {
        change(p*2,l,mid,x,num);
    }
    else
    {
        change(p*2+1,mid+1,r,x,num);
    }
    tree[p]=tree[p*2]+tree[p*2+1];
}
int query(int p,int l,int r, int x, int y)
{
    if(x<=l&&r<=y)
    return tree[p];
    int mid=(l+r)/2;
    if(y<=mid)
    return query(p*2,l,mid,x,y);
    if(x>mid)
    return query(p*2+1,mid+1,r,x,y);
    return (query(p*2,l,mid,x,y)+query(p*2+1,mid+1,r,x,y));
}
int main()
{
    ios::sync_with_stdio(false);
    int n,m,k;//n个元素,m次修改,k次询问
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
    cin>>arr[i];
    buildTree(1,1,n);
   while(m--)
    {
        int x,num;
        cin>>x>>num;
        change(1,1,n,x,num);
    }
    while(k--)
    {
        int x,y;
        cin>>x>>y;
        cout<<query(1,1,n,x,y)<<endl;
    }
    return 0;
}

线段树和树状数组的题单:https://ac.nowcoder.com/acm/problem/collection/621
参考资料:
1.https://www.cnblogs.com/YSFAC/p/7189571.html
2.https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html

全部评论

相关推荐

在debug的柠檬精很迷人:好消息:现在HR挑三拣四 15年后 HR跪着求要简历 坏消息:被挑的是这代人,到时候求人的也是这代人。真好。
点赞 评论 收藏
分享
避坑恶心到我了大家好,今天我想跟大家聊聊我在成都千子成智能科技有限公司(以下简称千子成)的求职经历,希望能给大家一些参考。千子成的母公司是“同创主悦”,主要经营各种产品,比如菜刀、POS机、电话卡等等。听起来是不是有点像地推销售公司?没错,就是那种类型的公司。我当时刚毕业,急需一份临时工作,所以在BOSS上看到了千子成的招聘信息。他们承诺无责底薪5000元,还包住宿,这吸引了我。面试的时候,HR也说了同样的话,感觉挺靠谱的。于是,我满怀期待地等待结果。结果出来后,我通过了面试,第二天就收到了试岗通知。试岗的内容就是地推销售,公司划定一个区域,然后你就得见人就问,问店铺、问路人,一直问到他们有意向为止。如果他们有兴趣,你就得摇同事帮忙推动,促进成交。说说一天的工作安排吧。工作时间是从早上8:30到晚上18:30。早上7点有人叫你起床,收拾后去公司,然后唱歌跳舞(销售公司都这样),7:55早课(类似宣誓),8:05同事间联系销售话术,8:15分享销售技巧,8:30经理训话。9:20左右从公司下市场,公交、地铁、自行车自费。到了市场大概10点左右,开始地推工作。中午吃饭时间大约是12:00,公司附近的路边盖饭面馆店自费AA,吃饭时间大约40分钟左右。吃完饭后继续地推工作,没有所谓的固定中午午休时间。下午6点下班后返回公司,不能直接下班,需要与同事交流话术,经理讲话洗脑。正常情况下9点下班。整个上班的一天中,早上到公司就是站着的,到晚上下班前都是站着。每天步数2万步以上。公司员工没有自己的工位,百来号人挤在一个20平方米的空间里听经理洗脑。白天就在市场上奔波,公司的投入成本几乎只有租金和工资,没有中央空调。早上2小时,晚上加班2小时,纯蒸桑拿。没有任何福利,节假日也没有3倍工资之类的。偶尔会有冲的酸梅汤和西瓜什么的。公司的晋升路径也很有意思:新人—组长—领队—主管—副经理—经理。要求是业绩和团队人数,类似传销模式,把人留下来。新人不能加微信、不能吐槽公司、不能有负面情绪、不能谈恋爱、不能说累。在公司没有任何坐的地方,不能依墙而坐。早上吃早饭在公司外面的安全通道,未到上班时间还会让你吃快些不能磨蹭。总之就是想榨干你。复试的时候,带你的师傅会给你营造一个钱多事少离家近的工作氛围,吹嘘工资有多高、还能吹自己毕业于好大学。然后让你早点来公司、无偿加班、抓住你可能不会走的心思进一步压榨你。总之,大家在找工作的时候一定要擦亮眼睛,避免踩坑!———来自网友
qq乃乃好喝到咩噗茶:不要做没有专业门槛的工作
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务