线段树模板+例题

简介: 线段树模板+例题

线段树是一种二叉搜索数,一般用来实现动态的区间询问,与树状数组有相似之处,但是能用树状数组实现的操作都能用线段树实现。


一般线段树用于以下几种操作:


建树,单点修改,区间查询,区间修改。


首先要进行的就是建树:


void bui(int id,int l,int r)
{
  if(l==r)
  {
  tr[id]=a[l];
  return ;
  }
  int mid=(l+r)/2;
  bui(id*2,l,mid);
  bui(id*2+1,mid+1,r);
  tr[id]=max(tr[id*2],tr[id*2+1]);//tr[id]=tr[id*2]+tr[id*2+1];
}//查询最大值与区间和,最小值同理

单点修改:


void gexi(int id,int l,int r,int x,int v)
{
  if(l==r)
  {
  tr[id]=v;
  return ;
  }
  int mid=(l+r)/2;
  if(x<=mid)
  gexi(id*2,l,mid,x,v);
  else 
  gexi(id*2+1,mid+1,r,x,v);
  tr[id]=max(tr[id*2],tr[id*2+1]);//tr[id]=tr[id*2]+tr[id*2+1]
}//查询最大值与区间和

区间查询:


int find(int id,int l,int r,int x,int y)
{
  if(x<=l&&r<=y)
  {
  return tr[id];
  }
  int mid=(l+r)/2,ans=0;
  if(x<=mid)
  ans=max(ans,find(id*2,l,mid,x,y));//ans+=find(id*2,l,mid,x,y);
  if(y>mid)
  ans=max(ans,find(id*2+1,mid+1,r,x,y));//ans+=find(id*2+1,mid+1,r,x,y);
  return ans;
}

区间修改:


void push_up(int id)
{
  tr[id]=tr[id*2]+tr[id*2+1];
}
void push_down(int id,int l,int r)
{
  if(lazy[id])//如果有lazy标记 
  {
  int mid=(l+r)/2;
  lazy[id*2]+=lazy[id];//左孩子的lazy加上它的lazy 
  lazy[id*2+1]+=lazy[id];//右孩子的lazy加上它的lazy 
  tr[id*2]+=lazy[id]*(mid-l+1);
  tr[id*2+1]+=lazy[id]*(r-mid);
  lazy[id]=0;//清除lazy标记 
  }
}
void qjgx(int id,int l,int r,int x,int y,int v)
{
  if(x<=l&&r<=y)//[l,r]被[x,y]包含了 
  }
  {
  lazy[id]+=v;//暂时不下传修改的值,加进lazy标记 
  tr[id]+=v*(r-l+1); 
  return ;
  }
  push_down(id,l,r);//要更新节点了,开始下传修改的值 
  int mid=(l+r)/2;
  if(x<=mid)
  qjgx(id*2,l,mid,x,y,v);//只有x<=mid(即[l,mid]有一部分是被[x,y]覆盖了的)才需要去更新[l,mid]
  if(y>mid)
  qjgx(id*2+1,mid+1,r,x,y,v);
  push_up(id); //子节点更新后父节点也更新 
}


下面是两道例题,可以试着尝试一下这几种操作


一:敌兵布阵


敌人有 N 个工兵营地,编号 1∼N。


初始时,第 i 个营地有 ai 个人。


接下来有若干个命令,命令有 4 种形式:


Add i j,i 和 j 为正整数,表示第 i 个营地增加 j 个人。(j 不超过 30)

Sub i j,i 和 j 为正整数,表示第 i 个营地减少 j 个人。(j 不超过 30)

Query i j,i 和 j 为正整数(i≤j),表示询问第 i 到第 j 个营地的总人数。

End,表示结束,此命令只会作为最后一条命令出现。

请你计算每个 Query 的答案。


输入格式

第一行包含整数 T,表示共有 T 组测试数据。


每组数据第一行包含一个整数 N。


第二行包含 N 个整数 a1,a2,…,aN。


接下来若干行,每行包含一条命令,格式如题目所述。


输出格式

对于第 i 组数据,首先输出一行 Case i:,然后对于每个 Query 询问,输出一行一个整数,表示询问的段中的总人数。


数据范围

1≤T≤10,

1≤N≤50000,

1≤ai≤50,

每组数据最多有 40000 条命令,

保证任何营地的人数都不会减少为负数。


输入样例:


1. 1
2. 10
3. 1 2 3 4 5 6 7 8 9 10
4. Query 1 3
5. Add 3 6
6. Query 2 7
7. Sub 10 2
8. Add 6 3
9. Query 3 10
10. End


输出样例:


1. Case 1:
2. 6
3. 33
4. 59


AC代码:

#include <bits/stdc++.h>
using namespace std;
#define p 50010
int tr[4*p],a[4*p];
void bui(int id,int l,int r)
{
  if(l==r)
  {
  tr[id]=a[l];
  return ;
  }
  int mid=(l+r)/2;
  bui(id*2,l,mid);
  bui(id*2+1,mid+1,r);
  tr[id]=tr[id*2]+tr[id*2+1];
}
int find(int id,int l,int r,int x,int y)
{
  if(x<=l&&r<=y)
  {
  return tr[id];
  }
  int mid=(l+r)/2,ans=0;
  if(x<=mid)
  ans+=find(id*2,l,mid,x,y);
  if(y>mid)
  ans+=find(id*2+1,mid+1,r,x,y);
  return ans;
}
void gexi(int id,int l,int r,int x,int v)
{
  if(l==r)
  {
  tr[id]+=v;
  return ;
  }
  int mid=(l+r)/2;
  if(x<=mid)
  gexi(id*2,l,mid,x,v);
  else 
  gexi(id*2+1,mid+1,r,x,v);
  tr[id]=tr[id*2]+tr[id*2+1];
}
int main()
{
  int t;
  scanf("%d",&t);
  for(int k=1;k<=t;k++)
  {
  int n;
  scanf("%d",&n);
  memset(a,0,sizeof(a));
  memset(tr,0,sizeof(tr));
  for(int i=1;i<=n;i++)
  scanf("%d",&a[i]);
  bui(1,1,n);
  char x[10];
  int aa,bb,cc;
  cout<<"Case "<<k<<":"<<endl;
  while(~scanf("%s",x))
  {
    if(x[0]=='Q')
    {
    scanf("%d%d",&aa,&bb);
    printf("%d\n",find(1,1,n,aa,bb));
    }
    else if(x[0]=='A')
    {
    scanf("%d%d",&aa,&bb);
    gexi(1,1,n,aa,bb);
    }
    else if(x[0]=='S')
    {
    scanf("%d%d",&aa,&bb);
    gexi(1,1,n,aa,-bb);
    }
    else
    break;
  }
  }
  return 0;
}


二:一个简单的整数问题2


给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:


C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。

Q l r,表示询问数列中第 l∼r 个数的和。

对于每个询问,输出一个整数表示答案。


输入格式

第一行两个整数 N,M。


第二行 N 个整数 A[i]。


接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。


输出格式

对于每个询问,输出一个整数表示答案。


每个答案占一行。


数据范围

1≤N,M≤105,

|d|≤10000,

|A[i]|≤109


输入样例:


1. 10 5
2. 1 2 3 4 5 6 7 8 9 10
3. Q 4 4
4. Q 1 10
5. Q 2 4
6. C 3 6 3
7. Q 2 4


输出样例:


1. 4
2. 55
3. 9
4. 15


AC代码:


#include <bits/stdc++.h>
#define int long long
using namespace std;
int sumv[10000001],n,m,a[10000001],lazy[10000001];
void push_up(int id)
{
      sumv[id] = sumv[id * 2] + sumv[id * 2 + 1];
}
void push_down(int id,int l,int r)
{
   if(lazy[id])
   {
     int mid = (l + r) / 2;
     lazy[id * 2] += lazy[id];
     lazy[id * 2 + 1] += lazy[id];
     sumv[id * 2] += lazy[id] * (mid - l + 1);
     sumv[id * 2 + 1] += lazy[id] * (r - mid);
     lazy[id] = 0;
   }
}
void bui(int id,int l,int r)
{
   if(l == r)
   {
     sumv[id] = a[l];
     return ;
   }
   int mid = (l + r) / 2;
   bui(id * 2,l,mid);
   bui(id * 2 + 1,mid + 1,r);
   sumv[id] = sumv[id * 2] + sumv[id * 2 + 1];
}
void qjgx(int id,int l,int r,int x,int y,int v)
{
   if(l >= x && r <= y)
   {
     lazy[id] += v;
     sumv[id] += v * (r - l + 1);
     return ;
   }
   push_down(id,l,r);
   int mid = (l + r) / 2;
   if(x <= mid) qjgx(id * 2,l,mid,x,y,v);
   if(y > mid) qjgx(id * 2 + 1,mid + 1,r,x,y,v);
   push_up(id);
}
int find(int id,int l,int r,int x,int y)
{
   if(x <= l && r <= y) return sumv[id];
   push_down(id,l,r);
   int mid = (l + r) / 2,ans = 0;
   if(x <= mid) ans += find(id * 2,l,mid,x,y);
   if(y > mid) ans += find(id * 2 + 1,mid + 1,r,x,y);
   return ans;
}
signed main()
{
   cin>>n>>m;
   for(int i = 1; i <= n; i++) cin>>a[i];
   bui(1,1,n);
   while(m--)
   {
    string p;
     int k,x,y;
     cin>>p>>x>>y;
     if(p == "C")
     {
       cin>>k;
       qjgx(1,1,n,x,y,k);
     }
     else cout<<find(1,1,n,x,y)<<'\n';
   }
  return 0;
}

目录
相关文章
|
7月前
|
机器学习/深度学习
【每日一题Day196】LC2106摘水果 | 枚举+前缀和数组 同向双指针+二分查找
【每日一题Day196】LC2106摘水果 | 枚举+前缀和数组 同向双指针+二分查找
58 0
|
6月前
|
算法 搜索推荐 C++
【洛谷 P1177】【模板】快速排序 题解(快速排序+数组索引)
**快速排序模板题目**,要求使用快排算法对输入的整数序列进行排序。输入包含正整数N和N个整数,输出排序后的序列。20%的数据N≤10^3,所有数据N≤10^5。代码中提供了一种实现,包括读取输入、定义partition函数划分数组、递归调用quickSort及主函数执行排序和输出。注意C++选手避免使用STL的`sort`。
34 0
|
6月前
|
机器学习/深度学习 搜索推荐 算法
【洛谷 P1177】【模板】快速排序 题解(快速排序+指针)
**快速排序模板题解** - **任务**:对输入的N个整数进行排序。 - **算法**:使用快速排序,避免使用C++的STL`sort`。 - **输入**:一行包含N(N≤10^5),第二行是N个不超过10^9的整数。 - **输出**:排序后的整数序列,空格分隔。 - **样例**:输入`5 4 2 4 5 1`,输出`1 2 4 4 5`。 - **提示**:20%的数据,N≤10^3;所有数据,N≤10^5。 - **代码**:定义`partition`函数划分数组,主函数`main`读取数据,调用`quickSort`排序,然后打印结果。
21 0
|
6月前
|
存储 C++
【洛谷 P1102】A-B 数对 题解(映射)
这是一个编程题目,要求计算给定正整数序列中满足 \( A - B = C \) 的数对个数。输入包含两行:正整数 \( N \) 和 \( C \),以及一串正整数。使用一个哈希映射记录每个数字出现的次数,然后遍历映射,如果找到 \( A = B + C \),则累加对应计数。样例输入输出为 \( N=4, C=1 \),数列为 \( 1 1 2 3 \),答案为 \( 3 \)。代码使用 C++ 实现,通过维护一个映射来存储数字频率并计算数对。
50 0
|
7月前
|
存储 算法
【PTA刷题】求链式线性表的倒数第K项(代码+详解)
【PTA刷题】求链式线性表的倒数第K项(代码+详解)
221 0
|
算法
代码随想录算法训练营第二十二天 | LeetCode 669. 修剪二叉搜索树、108. 将有序数组转换为二叉搜索树、538. 把二叉搜索树转换为累加树
代码随想录算法训练营第二十二天 | LeetCode 669. 修剪二叉搜索树、108. 将有序数组转换为二叉搜索树、538. 把二叉搜索树转换为累加树
54 0
【洛谷算法题】P5705-数字反转【入门1顺序结构】
【洛谷算法题】P5705-数字反转【入门1顺序结构】
|
算法
回溯算法 全排列模板
回溯算法 全排列模板
83 0
|
算法
算法练习题(二)——反转链表
算法练习题(二)——反转链表
92 0
算法练习题(二)——反转链表
(树状数组,线段树)(数组模拟哈希)(解题步骤)acwing数星星
(树状数组,线段树)(数组模拟哈希)(解题步骤)acwing数星星
96 0