单点修改的线段树
线段树的建树
建树:代表对你的线段树进行初始化,那么这个结构体的每一个值都要在build函数里面进行初始化赋值。
p代表二叉树访问到哪一个节点
l和r代表线段树当前而二叉树结点的左右端点
void build(int p,int l,int r){//p代表二叉树访问到哪一个节点
if(l==r){//访问到叶子节点
tr[p]={l,l,w[l]};
}else {
tr[p].l=l,tr[p].r=r;//一定要注意给每一个二叉树分支进行初始化
int mid=l+r>>1;//二叉树进行分支
build(p<<1,l,mid);//访问左子节点
build(p<<1|1,mid+1,r);//访问右子节点
pushup(p);//对线段树其他的信息进行运算
}
}
线段树的更新
x代表我要在哪一个点进行,增加v的操作
void update(int p,int x,int v){
if(tr[p].l==tr[p].r&&tr[p].r==x){
tr[p].sum+=v; //叶子节点
}else{//没有找到我们现在想要找到的点
int mid=tr[p].l+tr[p].r>>1;
if(x<=mid)
update(p<<1,x,v);//如果在左边,那么就向左子节点查找
else
update(p<<1|1,x,v);//向右子节点查找
pushup(p);//时刻更新线段树的值
}
}
线段树的区间查询操作
区间覆盖的方法进行查询
个人认为覆盖的方法来查询线段树比较好理解
ll query(int p,int l,int r){
//如果当前节点完全是查询区间的真子集那么就返回当前值
if(tr[p].l>=l&&tr[p].r<=r){
return tr[p].sum;
}
//如果不是那就左右查询
else {
ll t=0;
int mid=tr[p].l+tr[p].r>>1;
if(l<=mid)t+=query(p<<1,l,r);
if(r>mid)t+=query(p<<1|1,l,r);
return t;
}
}
区间相等的方法进行查询
ll query(int p,int l,int r){
if(tr[p].l==l&&tr[p].r==r){
return tr[p].sum; //刚好查到我们现在的区间
}
else {
int mid=tr[p].l+tr[p].r>>1;
if(r<=mid)return query(p<<1,l,r);//如果和右子节点没关系,那就不用遍历
else if(l>mid)return query(p<<1|1,l,r);
else return query(p<<1|1,mid+1,r)+query(p<<1,l,mid);//如果两边都有关系,那就还是需要遍历一下
}
}
例题1:敌兵布阵
#include<iostream>
#include<string>
using namespace std;
typedef long long ll;
ll w[2102100];
string qus="Query";
string miu="Sub";
string plu="Add";
string endd="End";
struct Segmentree{
int l,r;
ll sum;
}tr[210210];
void pushup(Segmentree &fa,Segmentree &son1,Segmentree &son2){
fa.sum=son1.sum+son2.sum;
}
void pushup(int p){
pushup(tr[p],tr[p<<1],tr[p<<1|1]);
}
void build(int p,int l,int r){
if(l==r){
tr[p]={l,l,w[l]};
// cout<<tr[p].sum<<" ";
}
else {
tr[p].l=l,tr[p].r=r;
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
}
void update(int p,int x,int v){
if(tr[p].l==tr[p].r&&tr[p].r==x){
tr[p].sum+=v;
}else{
int mid=tr[p].l+tr[p].r>>1;
if(x<=mid)
update(p<<1,x,v);
else
update(p<<1|1,x,v);
pushup(p);
}
}
ll query(int p,int l,int r){
if(tr[p].l>=l&&tr[p].r<=r){
// cout<<tr[p].l<<" "<<tr[p].sum<<"\n";
return tr[p].sum;
}
else {
ll t=0;
int mid=tr[p].l+tr[p].r>>1;
if(l<=mid)t+=query(p<<1,l,r);
if(r>mid)t+=query(p<<1|1,l,r);
// cout<<tr[p].l<<" "<<tr[p].r<<" "<<tr[p].sum<<"\n";
return t;
}
}
int main(){
int T;
cin>>T;
int test=0;
while(T--){
test++;
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
build(1,1,n);
string t;int l,r;
cout<<"Case "<<test<<":"<<endl;
while(cin>>t&&t!=endd){
scanf("%d %d",&l,&r);
//cin>>l>>r;
if(t==plu){
w[l]+=r;
update(1,l,r);
}else if(t==qus){
// cout<<l<<" "<<r<<"\n";
printf("%lld\n",query(1,l,r));//-query(1,1,l)+w[l]
}else if(t==miu){
w[l]-=r;
update(1,l,-r);
}
}
}
}
例题2:区间最大公约数
题目传送门
其实根据欧几里得定理:
g
c
d
(
x
,
y
)
=
g
c
d
(
x
,
y
−
x
)
gcd(x,y)=gcd(x,y-x)
gcd(x,y)=gcd(x,y−x)
拓展到多维那就是
g
c
d
(
x
,
y
,
z
)
=
g
c
d
(
x
,
y
−
x
,
z
−
y
)
gcd(x,y,z)=gcd(x,y-x,z-y)
gcd(x,y,z)=gcd(x,y−x,z−y)
比如我现在有n个数,后边的n-1个数都是差分序列啊,除了第一个数是它本身!这个性质很重要!因此我们需要开两个整数,一个存差分序列,还有一个当然是存sum,当然还有一个常识就是
g
c
d
(
x
,
y
,
z
)
=
g
c
d
(
g
c
d
(
x
,
y
)
,
z
)
gcd(x,y,z)=gcd(gcd(x,y),z)
gcd(x,y,z)=gcd(gcd(x,y),z)
#include<iostream>
#include<string>
using namespace std;
typedef long long ll;
const ll N=505050;
ll w[N];
struct Segmentree{
ll l,r;
ll _gcd,sum;
}tr[4*N];
ll gcd(ll a,ll b){
return b?gcd(b,a%b):a;
}
void pushup(Segmentree &fa,Segmentree &l,Segmentree &r){
fa.sum=l.sum+r.sum;
fa._gcd=gcd(l._gcd,r._gcd);
}
void pushup(ll p){
tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
tr[p]._gcd=gcd(tr[p<<1|1]._gcd,tr[p<<1]._gcd);
}
void build(ll p,ll l,ll r){
if(l==r){
tr[p].l=l;
tr[p].r=l;
tr[p].sum=w[l]-w[l-1];
tr[p]._gcd=w[l]-w[l-1];
return ;
}
tr[p].l=l;
tr[p].r=r;
ll mid=l+r>>1;
if(r>=1+mid)build(p<<1|1,mid+1,r);
if(l<=mid)build(p<<1,l,mid);
pushup(p);
}
void update(ll p,ll x,ll u){
if(tr[p].l==tr[p].r&&tr[p].l==x){
tr[p].sum+=u;tr[p]._gcd=tr[p].sum;
return;
}
ll mid=tr[p].l+tr[p].r>>1;
if(x<=mid){
update(p<<1,x,u);
}
if(x>mid)update(p<<1|1,x,u);
pushup(p);
return;
}
Segmentree query(ll p,ll l,ll r){
if(tr[p].l>=l&&tr[p].r<=r)return tr[p];
ll mid=tr[p].l+tr[p].r>>1;
if(l>=mid+1){
return query(p<<1|1,l,r);
}
else if(r<=mid)return query(p<<1,l,r);
else {
auto left =query(p<<1,l,r);
auto right=query(p<<1|1,l,r);
Segmentree t;
pushup(t,left,right);
return t;
}
}
int main(){
ll n,m;
cin>>n>>m;
for(ll i=1;i<=n;i++)cin>>w[i];
build(1,1,n);
while(m--){
char c;
getchar();
cin>>c;
if(c=='Q'){
ll l,r;
//auto right({0, 0, 0, 0});
cin>>l>>r;auto t=query(1,1,l);
printf("%lld\n",abs(gcd(query(1,l+1,r)._gcd,t.sum)));
}else{
ll l,r,d;
cin>>l>>r>>d;
update(1,l,d);
if(r<n){
update(1,r+1,-d);
}
}
}
}
区间修改的线段树
上述我们说的线段树,有时候会涉及到区间修改的问题,但是虽然说有一些情况下,我们可以利用差分数组来把这个区间修改优化成单点修改,但是很多情况下我们无法用差分数组,如果还是利用单点修改的线段树,这样就会tle到飞起 ~ 这样的情况下我么就应该引入“懒标记”。
线段树的区间更新操作
当然是懒标记,懒标记实际上就是我们对遍历到的这个区间做一个标记,然后等以后要用的时候,用到哪个区间就把这个懒标记下传,懒标记所到之处,就更新这个线段树节点的相应的值。
可以说是非常巧妙的一个操作。
区间修改的线段树的声明
struct Segmentree{
ll l,r;
ll sum;
ll lz;
}tr[1021000];
lz存放的是这个区间每一个值都应该加上lz这么多值。这个就是懒标记!
懒标记的更新和上传
懒标记的上传
切记上传完懒标记了以后一定要更新这个懒标记的值为0
void pushdown(Segmentree &fa,Segmentree &son1,Segmentree &son2){
son1.lz+=fa.lz;son1.sum+=(son1.r-son1.l+1)*fa.lz;
son2.lz+=fa.lz;son2.sum+=(son2.r-son2.l+1)*fa.lz;
fa.lz=0;
}
void pushdown(ll p){
if(tr[p].lz)pushdown(tr[p],tr[p<<1],tr[p<<1|1]);
}
懒标记的更新
一定要记住就是先更新懒标记(上传),然后再合并区间。
void update(ll p,ll l,ll r,ll v){
if(tr[p].l>=l&&tr[p].r<=r){
tr[p].sum+=(ll)(tr[p].r-tr[p].l+1)*v;//更新节点值
tr[p].lz+=v;//更新懒标记
}else {
pushdown(p);
ll mid=tr[p].l+tr[p].r>>1;
if(l<=mid)
update(p<<1,l,r,v);
if(r>mid)update(p<<1|1,l,r,v);
pushup(p);
}
}
线段树的建树
void build(ll p,ll l,ll r){
if(l==r){
tr[p]={l,l,w[l],0};
}else {
tr[p].l=l,tr[p].r=r;
ll mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
}
区间修改的线段树的查询操作
ll query(ll p,ll l,ll r){
if(tr[p].l>=l&&tr[p].r<=r){
return tr[p].sum;
}else {
pushdown(p);
ll mid=tr[p].l+tr[p].r>>1;
ll k=0;
if(l<=mid)
k+=query(p<<1,l,r);
if(r>mid)k+=query(p<<1|1,l,r);
return k;
}
}
例题1:A Simple Problem with Integers
#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
typedef long long ll;
ll w[2102100];
struct Segmentree{
ll l,r;
ll sum;
ll lz;
}tr[1021000];
void pushup(Segmentree &fa,Segmentree &son1,Segmentree &son2){
fa.sum=son1.sum+son2.sum;
}
void pushup(ll p){
pushup(tr[p],tr[p<<1],tr[p<<1|1]);
}
void pushdown(Segmentree &fa,Segmentree &son1,Segmentree &son2){
son1.lz+=fa.lz;son1.sum+=(son1.r-son1.l+1)*fa.lz;
son2.lz+=fa.lz;son2.sum+=(son2.r-son2.l+1)*fa.lz;
fa.lz=0;
}
void pushdown(ll p){
if(tr[p].lz)pushdown(tr[p],tr[p<<1],tr[p<<1|1]);
}
void build(ll p,ll l,ll r){
if(l==r){
tr[p]={l,l,w[l],0};
}else {
tr[p].l=l,tr[p].r=r;
ll mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
}
void update(ll p,ll l,ll r,ll v){
if(tr[p].l>=l&&tr[p].r<=r){
tr[p].sum+=(ll)(tr[p].r-tr[p].l+1)*v;//更新节点值
tr[p].lz+=v;//更新懒标记
}else {
pushdown(p);
ll mid=tr[p].l+tr[p].r>>1;
if(l<=mid)
update(p<<1,l,r,v);
if(r>mid)update(p<<1|1,l,r,v);
pushup(p);
}
}
ll query(ll p,ll l,ll r){
if(tr[p].l>=l&&tr[p].r<=r){
return tr[p].sum;
}else {
pushdown(p);
ll mid=tr[p].l+tr[p].r>>1;
ll k=0;
if(l<=mid)
k+=query(p<<1,l,r);
if(r>mid)k+=query(p<<1|1,l,r);
return k;
}
}
int main(){
ll n,m;
cin>>n>>m;
for(ll i=1;i<=n;i++)cin>>w[i];
build(1,1,n);
while(m--){
char a;
getchar();
cin>>a;
ll l,r;
cin>>l>>r;
if(a=='Q'){
printf("%lld\n",query(1,l,r));
}else {
ll p;
cin>>p;
update(1,l,r,p);
}
}
}