例题——洛谷P3374
题目描述
如题,已知一个数列,你需要进行下面两种操作:
将某一个数加上 x
求出某区间每一个数的和
输入格式
第一行包含两个正整数 n,m分别表示该数列数字的个数和操作的总个数。
第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:
1 x k 含义:将第 x 个数加上 k
2 x y 含义:输出区间 [x,y]内每个数的和
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
输入输出样例
输入
5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
输出
14
16
【数据范围】
对于100% 的数据,1≤n,m≤5×10^5
思路与分析
很明显我们要对一段给出的区间进行操作,对区间求和,然后单点修改, 如果没有单点修改,我们用前缀和就能实现O(n)的时间复杂度,但是涉及到了修改,前缀和就显得无能为力,就需要O(n^2)的时间复杂度,所以我们引入树形结构来进行这个维护和查询工作,单次查询O(logn).
线段树
基础线段树模板一共有三种操作,建树,修改和查询。除了建树是O(n)外,其他都为O(logn),虽然修改操作从O(1)变到了二分查找,但是因为特殊的树形结构使得查询也是O(logn)。
树形结构的定义
typedef long long ll;
struct node
{
int l,r;
ll sum;
};
ll arr[500100];
node tree[2000020];
建树
选择递归来建树
void build(int p,int l,int r)//p是树形数组里的下表
{
tree[p].l=l;
tree[p].r=r;
if(l==r)
{
tree[p].sum=arr[l];
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build((p<<1)|1,mid+1,r);
tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;//维护和
}
查询
ll _find(int p,int l,int r)
{
ll sum=0;
if(l<=tree[p].l&&tree[p].r<=r)
return tree[p].sum;
if(tree[p].r<l||tree[p].l>r)
return 0;
if(tree[(p<<1)|1].l<=r)
sum+= _find((p<<1)|1,l,r);
if(tree[p<<1].r>=l)
sum+= _find(p<<1,l,r);
return sum;
}
单点修改
void add(int x,int p,ll k)
{
if(tree[p].l==tree[p].r)
{
tree[p].sum+=k;
return ;
}
if(x<=tree[p<<1].r)
add(x,p<<1,k);
else
add(x,(p<<1)|1,k);
tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
return ;
}
例题完整代码
#include<iostream>
using namespace std;
typedef long long ll;
struct node
{
int l,r;
ll sum;
};
ll arr[500100];
node tree[2000020];
void build(int p,int l,int r)
{
tree[p].l=l;
tree[p].r=r;
if(l==r)
{
tree[p].sum=arr[l];
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build((p<<1)|1,mid+1,r);
tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
}
ll _find(int p,int l,int r)
{
ll sum=0;
if(l<=tree[p].l&&tree[p].r<=r)
return tree[p].sum;
if(tree[p].r<l||tree[p].l>r)
return 0;
if(tree[(p<<1)|1].l<=r)
sum+= _find((p<<1)|1,l,r);
if(tree[p<<1].r>=l)
sum+= _find(p<<1,l,r);
return sum;
}
void add(int x,int p,ll k)
{
if(tree[p].l==tree[p].r)
{
tree[p].sum+=k;
return ;
}
if(x<=tree[p<<1].r)
add(x,p<<1,k);
else
add(x,(p<<1)|1,k);
tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
return ;
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>arr[i];
build(1,1,n);
for(int i=0;i<m;i++)
{
ll a,b,c;
cin>>a>>b>>c;
if(a==1)
add(b,1,c);
else
cout<<_find(1,b,c)<<endl;
}
}
Lazy懒标记 洛谷P3368
题目描述
如题,已知一个数列,你需要进行下面两种操作:
将某区间每一个数数加上 x;求出某一个数的值。
输入格式
第一行包含两个整数 N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含 N 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 M 行每行包含 2 或 4个整数,表示一个操作,具体如下:
操作 1: 格式:1 x y k 含义:将区间 [x,y] 内每个数加上 kk;
操作 2: 格式:2 x 含义:输出第 x 个数的值。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
输入输出样例
输入
5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4
输出
6
10
定义
typedef long long ll;
struct node
{
int l,r;
ll sum;
int lazy;
};
int arr[500010];
node tree[2000000];
建树
void build(int p,int l,int r)
{
tree[p].l=l;
tree[p].r=r;
if(l==r)
{
tree[p].sum=arr[l];
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build((p<<1)|1,mid+1,r);
tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
}
pushdown函数
void pushdown(int p)
{
int k=tree[p].lazy;
if(k!=0)
{
tree[p<<1].lazy+=k;
tree[(p<<1)|1].lazy+=k;
tree[p<<1].sum+=k*(tree[p<<1].r-tree[p<<1].l+1);
tree[(p<<1)|1].sum+=k*(tree[(p<<1)|1].r-tree[(p<<1)|1].l+1);
tree[p].lazy=0;
}
return ;
}
区间修改
void add(int p,int l,int r,int k)//递归到p,给l->r区间内统一加上k
{
if(l<=tree[p].l&&tree[p].r<=r)
{
tree[p].lazy+=k;
tree[p].sum+=k*(tree[p].r-tree[p].l+1);
return ;
}
pushdown(p);
if(tree[p<<1].r>=l)
add(p<<1,l,r,k);
if(tree[(p<<1)|1].l<=r)
add((p<<1)|1,l,r,k);
tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
return ;
}
单点/区间查找
ll _find(int p,int l,int r)
{
if(tree[p].l>=l&&tree[p].r<=r)
return tree[p].sum;
if(tree[p].r<l|| tree[p].l>r)
return 0;
pushdown(p);
ll s=0;
if(tree[p<<1].r>=l)
s+=_find(p<<1,l,r);
if(tree[(p<<1)|1].l<=r)
s+=_find((p<<1)|1,l,r);
return s;
}
最终函数
#include<iostream>
using namespace std;
typedef long long ll;
struct node
{
int l,r;
ll sum;
int lazy;
};
int arr[500010];
node tree[2000000];
void build(int p,int l,int r)
{
tree[p].l=l;
tree[p].r=r;
if(l==r)
{
tree[p].sum=arr[l];
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build((p<<1)|1,mid+1,r);
tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
}
void pushdown(int p)
{
int k=tree[p].lazy;
if(k!=0)
{
tree[p<<1].lazy+=k;
tree[(p<<1)|1].lazy+=k;
tree[p<<1].sum+=k*(tree[p<<1].r-tree[p<<1].l+1);
tree[(p<<1)|1].sum+=k*(tree[(p<<1)|1].r-tree[(p<<1)|1].l+1);
tree[p].lazy=0;
}
return ;
}
void add(int p,int l,int r,int k)//递归到p,给l->r区间内统一加上k
{
if(l<=tree[p].l&&tree[p].r<=r)
{
tree[p].lazy+=k;
tree[p].sum+=k*(tree[p].r-tree[p].l+1);
return ;
}
pushdown(p);
if(tree[p<<1].r>=l)
add(p<<1,l,r,k);
if(tree[(p<<1)|1].l<=r)
add((p<<1)|1,l,r,k);
tree[p].sum=tree[p<<1].sum+tree[(p<<1)|1].sum;
return ;
}
ll _find(int p,int l,int r)
{
if(tree[p].l>=l&&tree[p].r<=r)
return tree[p].sum;
if(tree[p].r<l|| tree[p].l>r)
return 0;
pushdown(p);
ll s=0;
if(tree[p<<1].r>=l)
s+=_find(p<<1,l,r);
if(tree[(p<<1)|1].l<=r)
s+=_find((p<<1)|1,l,r);
return s;
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>arr[i];
build(1,1,n);
for(int i=0;i<m;i++)
{
int x;
cin>>x;
if(x==1)
{
int a,b,c;
cin>>a>>b>>c;
add(1,a,b,c);
}
else
{
int a,b;
cin>>a;
cout<<_find(1,a,a)<<endl;
}
}
}