洛谷:3372 线段树 结构体版本+数组版本

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 �k。
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 �,�n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 �n 个用空格分隔的整数,其中第 �i 个数字表示数列第 �i 项的初始值。

接下来 �m 行每行包含 33 或 44 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 [�,�][x,y] 内每个数加上 �k。
  2. 2 x y:输出区间 [�,�][x,y] 内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

输入输出样例

输入 #1复制

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

输出 #1复制

11
8
20
下面提供2种版本,结构体版本,数组版本 的模板,
3372 线段树 结构体版本+数组版本 模板,区间更新,区间查询
 // 3372 线段树 懒人标记版本  结构体版本 
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
ll a[N];
struct segment{
ll val;
int l,r;
int tag;
}tree[N*4];
//计算根结点的值 
void push_up(int rt)
{
tree[rt].val=tree[rt*2].val+tree[rt*2+1].val;
// 创建线段树 
void build(ll rt,ll left,ll right) // rt为根,left right为左右界限 
{
tree[rt].l=left;
tree[rt].r=right;
tree[rt].tag=0;
if(tree[rt].l==tree[rt].r) //只有一个结点的时候,树的值就是它本身元素 
{
tree[rt].val=a[left];
return ;
}
//多余一个结点,分2半,左右分别建立树,然后先上更新根
ll mid=(left+right)/2; 
build(rt*2,left,mid);
build(rt*2+1,mid+1,right);
push_up(rt);//更新根 
}
// 修改懒人标记 tag
void addtag(ll rt,ll left,ll right,ll d)// left-right 增加d 
{
tree[rt].tag+=d;//根懒人标记:增加d ,根节点的标记增加d 
tree[rt].val+=(right-left+1)*d;//树根的值 增加  数倍的d
 } 
 void push_down(ll rt,ll left ,ll right)
 {
 if(tree[rt].tag>0) // 需要修改了,向下传导,递归修改 
 {
 ll mid=(left+right)/2;
 addtag(rt*2,left,mid,tree[rt].tag); // 增加的值为根节点的tag值 
 addtag(rt*2+1,mid+1,right,tree[rt].tag);
 tree[rt].tag=0;//然后自己变成0 
}
 }
 void update(ll l,ll r,ll rt,ll left,ll right ,ll d) //l-r 的值区间增加d 
 {
 if(l<=left&&r>=right)//全面覆盖
{
addtag(rt,left,right,d); //直接更新 后返回, 
return ;
// 不能够全部覆盖,向下到
push_down(rt,left,right);
  ll mid=(left+right)/2;
  if(l<=mid) // 包含左边 
  update(l,r,rt*2,left,mid,d);
  if(r>mid)  //包含右边
  update(l,r,rt*2+1,mid+1,right,d);
  push_up(rt);//然后在更新根 
 ll query(ll l,ll r,ll rt,ll left,ll right)
 {
 if(l<=left&&r>=right) // 全部包含 直接返回 树根 
 {
 return tree[rt].val;
}
push_down(rt,left,right);
ll mid=(left+right)/2;
ll ans=0;
if(l<=mid)
ans+=query(l,r,2*rt,left,mid);
if(r>mid)
{
ans+=query(l,r,2*rt+1,mid+1,right);
}
return ans;
 }
 int main()
 {
 ll n,m;
 scanf("%lld %lld",&n,&m);
 for(ll i=1;i<=n;i++)
 {
 scanf("%lld",&a[i]);
}
// 建立树
build(1,1,n);//树根为1,访问1-n
while(m--)
{
ll q,l,r,d;
scanf("%lld",&q);
if(q==1) // 更新  
{
scanf("%lld%lld%lld",&l,&r,&d);
update(l,r,1,1,n,d);// 根为1,范围 l-r, 
}
else
{
scanf("%lld%lld",&l,&r);
printf("%lld\n",query(l,r,1,1,n));
}
return 0; 
 }
 /*
 // 3372 线段树 懒人标记版本 
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
ll a[N];
ll tree[N*4];
ll tag[N*4];//线段树一般需要4倍的空间
//计算根结点的值 
void push_up(int rt)
{
tree[rt]=tree[rt*2]+tree[rt*2+1];
// 创建线段树 
void build(ll rt,ll left,ll right) // rt为根,left right为左右界限 
{
tag[rt]=0;
if(left==right) //只有一个结点的时候,树的值就是它本身元素 
{
tree[rt]=a[left];
return ;
}
//多余一个结点,分2半,左右分别建立树,然后先上更新根
ll mid=(left+right)/2; 
build(rt*2,left,mid);
build(rt*2+1,mid+1,right);
push_up(rt);//更新根 
}
// 修改懒人标记 tag
void addtag(ll rt,ll left,ll right,ll d)// left-right 增加d 
{
tag[rt]+=d;//根懒人标记:增加d
tree[rt]+=(right-left+1)*d;//树根的值 增加  数倍的d
 } 
 void push_down(ll rt,ll left ,ll right)
 {
 if(tag[rt]>0) // 需要修改了,向下传导,递归修改 
 {
 ll mid=(left+right)/2;
 addtag(rt*2,left,mid,tag[rt]); // 增加的值为根节点的tag值 
 addtag(rt*2+1,mid+1,right,tag[rt]);
 tag[rt]=0;//然后自己变成0 
}
 }
 void update(ll l,ll r,ll rt,ll left,ll right ,ll d) //l-r 的值区间增加d 
 {
 if(l<=left&&r>=right)//全面覆盖
{
addtag(rt,left,right,d); //直接更新 后返回, 
return ;
// 不能够全部覆盖,向下到
push_down(rt,left,right);
  ll mid=(left+right)/2;
  if(l<=mid) // 包含左边 
  update(l,r,rt*2,left,mid,d);
  if(r>mid)  //包含右边
  update(l,r,rt*2+1,mid+1,right,d);
  push_up(rt);//然后在更新根 
 ll query(ll l,ll r,ll rt,ll left,ll right)
 {
 if(l<=left&&r>=right) // 全部包含 直接返回 树根 
 {
 return tree[rt];
}
push_down(rt,left,right);
ll mid=(left+right)/2;
ll ans=0;
if(l<=mid)
ans+=query(l,r,2*rt,left,mid);
if(r>mid)
{
ans+=query(l,r,2*rt+1,mid+1,right);
}
return ans;
 }
 int main()
 {
 ll n,m;
 scanf("%lld %lld",&n,&m);
 for(ll i=1;i<=n;i++)
 {
 scanf("%lld",&a[i]);
}
// 建立树
build(1,1,n);//树根为1,访问1-n
while(m--)
{
ll q,l,r,d;
scanf("%lld",&q);
if(q==1) // 更新  
{
scanf("%lld%lld%lld",&l,&r,&d);
update(l,r,1,1,n,d);// 根为1,范围 l-r, 
}
else
{
scanf("%lld%lld",&l,&r);
printf("%lld\n",query(l,r,1,1,n));
}
 }
 */ 
版本3;
// 线段树 3372 模板题目 
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
ll a[N];
ll tree[N*4];
ll tag[N*4];//线段树一般需要4倍的空间
//计算根结点的值 
void push_up(int rt)
{
tree[rt]=tree[rt*2]+tree[rt*2+1];
// 创建线段树 
void build(ll rt,ll left,ll right) // rt为根,left right为左右界限 
{
tag[rt]=0;
if(left==right) //只有一个结点的时候,树的值就是它本身元素 
{
tree[rt]=a[left];
return ;
}
//多余一个结点,分2半,左右分别建立树,然后先上更新根
ll mid=(left+right)/2; 
build(rt*2,left,mid);
build(rt*2+1,mid+1,right);
push_up(rt);//更新根 
}
// 修改懒人标记 tag
//void addtag(ll rt,ll left,ll right,ll d)// left-right 增加d 
//{
// tag[rt]+=d;//根懒人标记:增加d
// tree[rt]+=(right-left+1)*d;//树根的值 增加  数倍的d 
// } 
/*
//add[rt*2]+=add[rt];
//add[rt*2+1]+=add[rt];
//sum[rt*2]+=(m-m/2)*add[rt];  // 例如1-3 左边1-2 右边为3 ,左边3-3/2
//sum[rt*2+1]+=m/2*add[rt]; 
*/
 void push_down(ll rt,int m) //更新的次数为m 
 {
  if(tag[rt]>0) // 需要修改了,向下传导,递归修改 
  {
  tag[rt*2]+=tag[rt];
  tag[rt*2+1]+=tag[rt];
  tree[rt*2]+=(m-m/2)*tag[rt];
  tree[rt*2+1]+=m/2*tag[rt];
  // ll mid=(left+right)/2;
  // addtag(rt*2,left,mid,tag[rt]); // 增加的值为根节点的tag值 
  // addtag(rt*2+1,mid+1,right,tag[rt]);
  tag[rt]=0;//然后自己变成0 
 }
 }
 void update(ll l,ll r,ll rt,ll left,ll right ,ll d) //l-r 的值区间增加d 
 {
  if(l<=left&&r>=right)//全面覆盖
 {
  //sum[rt]+=(r-l+1)*c;
  // add[rt]+=c;
  tree[rt]+=(right-left+1)*d;
  tag[rt]+=d;
  //addtag(rt,left,right,d); //直接更新 后返回, 
  return ;
 } 
 // 不能够全部覆盖,向下到
 //push_down(rt,left,right);
 push_down(rt,right-left+1);
  ll mid=(left+right)/2;
  if(l<=mid) // 包含左边 
  update(l,r,rt*2,left,mid,d);
  if(r>mid)  //包含右边
  update(l,r,rt*2+1,mid+1,right,d);
  push_up(rt);//然后在更新根 
 ll query(ll l,ll r,ll rt,ll left,ll right)
 {
  if(l<=left&&r>=right) // 全部包含 直接返回 树根 
  {
  return tree[rt];
 }
 //push_down(rt,left,right);
 push_down(rt,right-left+1);
 ll mid=(left+right)/2;
 ll ans=0;
 if(l<=mid)
 ans+=query(l,r,2*rt,left,mid);
 if(r>mid)
 {
  ans+=query(l,r,2*rt+1,mid+1,right);
 }
 return ans;
 }
 int main()
 {
  ll n,m;
  scanf("%lld %lld",&n,&m);
  for(ll i=1;i<=n;i++)
  {
  scanf("%lld",&a[i]);
}
// 建立树
build(1,1,n);//树根为1,访问1-n
while(m--)
{
ll q,l,r,d;
scanf("%lld",&q);
if(q==1) // 更新  
{
scanf("%lld%lld%lld",&l,&r,&d);
update(l,r,1,1,n,d);// 根为1,范围 l-r, 
}
else
{
scanf("%lld%lld",&l,&r);
printf("%lld\n",query(l,r,1,1,n));
}
 } 
 return 0;
 }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值