线段树入门练习

Powered by:AB_IN 局外人

强推这篇线段树详解,讲的很全面。
戳这!!

  • 线段树的构造函数一般有三个
    • Build (建树)
    • Update(更新值)
    • Query(查询值)
  • 要开四倍的数组储存数据tr[maxn<<2]
  • 如果涉及到区间更新,会用到
    • pushup(更新父节点)
    • pushdown (涉及到懒标记)
    • ADD MUL MAX MIN 等配合pushdown操作的函数,根据题目的具体要求。
  • 父节点 i i i的两个子树下标为 : 左 : 2 ∗ i = i < < 1 左: 2*i=i<<1 :2i=i<<1 右 : 2 ∗ i + 1 = i < < 1 ∣ 1 右: 2*i+1=i<<1|1 :2i+1=i<<11

大体知道了这些,再看几遍板子,基本就可以写出属于自己的板子了。

P3372 【模板】线段树 1

带注释的板子。

#include 
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll a[maxn<<2],tr[maxn<<2],add_tag[maxn<<2];
//  原数组     线段树      加法lazy_tag标记  
ll n,m,p,op1,x,y,k;

inline void pushup(ll i)//更新函数,也可以最大值,最小值。
{
    tr[i]=(tr[i<<1]+tr[i<<1|1]);
    //比如t[i] = max(t[i<<1],t[i<<1|1]);
}

void bulid(ll i,ll l,ll r)//下面主函数i=1为固定的写法
{
    //i为当前需要建立的结点,l为当前需要建立区间的左端点,r则为右端点
    add_tag[i]=0;//tag数组初始化
    if(l==r){//如果左右端点相等,即为叶子结点,直接赋值即可
        tr[i]=a[l];//注意这里是l
        return;
    }
    ll mid=(l+r)>>1;
    bulid(i<<1,l,mid);//递归构造左子树
    bulid(i<<1|1,mid+1,r);//递归构造右子树
    pushup(i);//更新父节点,因为先是从上至下建树,再从下至上回溯,pushup相当于往上更新父节点的值。
}

inline void ADD(ll i,ll l,ll r,ll k)//[l,r]区间所有值加k
{
    add_tag[i]=(add_tag[i]+k);//这么写方便取余
    tr[i]=(tr[i]+(r-l+1)*k);//tr[i]是[l,r]值的和,所以加上(r-l+1)个k
}

inline void pushdown(ll i,ll l,ll r,ll mid)//下传懒标记到左右子树
{
    if((!add_tag[i])) return ;
    ADD(i<<1,l,mid,add_tag[i]);
    ADD(i<<1|1,mid+1,r,add_tag[i]);
    add_tag[i]=0; //清除标记
}

inline void update_ADD (ll i,ll l,ll r,ll x,ll y,ll k)//更新[x,y]区间,都加上k。
{
    //[l,r]为总区间,[x,y]为更新区间,通过不断二分递归[l,r],让l,r在[x,y]中,这样的区间就符合条件组成[x,y]
    //[x,y]是定死的,是改变l,r的值
    if(l>y||r<x) return;
    if(l>=x&&r<=y) return ADD(i,l,r,k);//相当于去执行这项命令
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    update_ADD(i<<1,l,mid,x,y,k);
    update_ADD(i<<1|1,mid+1,r,x,y,k);
    pushup(i);
}

ll query(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i];//符合条件时,返回下标对应的值即可。
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    if(x<=mid) res=res+query(i<<1,l,mid,x,y);
    if(y>mid)  res=res+query(i<<1|1,mid+1,r,x,y);
    return res;
}


namespace IO{
    char ibuf[1<<21],*ip=ibuf,*ip_=ibuf;
    char obuf[1<<21],*op=obuf,*op_=obuf+(1<<21);
    inline char gc(){
        if(ip!=ip_)return *ip++;
        ip=ibuf;ip_=ip+fread(ibuf,1,1<<21,stdin);
        return ip==ip_?EOF:*ip++;
    }
    inline void pc(char c){
        if(op==op_)fwrite(obuf,1,1<<21,stdout),op=obuf;
        *op++=c;
    }
    inline ll read(){
        register ll x=0,ch=gc(),w=1;
        for(;ch<'0'||ch>'9';ch=gc())if(ch=='-')w=-1;
        for(;ch>='0'&&ch<='9';ch=gc())x=x*10+ch-48;
        return w*x;
    }
    template<class I>
    inline void write(I x){
        if(x<0)pc('-'),x=-x;
        if(x>9)write(x/10);pc(x%10+'0');
    }
    class flusher_{
    public:
        ~flusher_(){if(op!=obuf)fwrite(obuf,1,op-obuf,stdout);}
    }IO_flusher;
}
using namespace IO;

int main()
{
    //cin>>n>>m;
    n=read();m=read();
    rep(i,1,n) a[i]=read();//cin>>a[i];
    bulid(1,1,n);
    rep(i,1,m){
        //cin>>op1;
        op1=read();
        if(op1==1){
            //cin>>x>>y>>k;
            x=read();y=read();k=read();
            update_ADD(1,1,n,x,y,k);
        }
        if(op1==2){
            //cin>>x>>y;
            x=read();y=read();
            printf("%lld\n",query(1,1,n,x,y));
        }
    }
    return 0;
}

P3373 【模板】线段树 2

秉持先乘后加的原则。
有乘法时,add_tag,mul_tag,tr都得变化,因为乘法优先级大,应该先算乘法,所以之前懒标记 加和乘 的值也得乘上。

#include 
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll a[maxn<<2],tr[maxn<<2],add_tag[maxn<<2],    mul_tag[maxn<<2];
//  原数组     线段树      加法lazy_tag标记    乘法lazy_tag标记
ll n,m,p,op1,x,y,k;

inline void pushup(ll i)//更新函数,也可以最大值,最小值。
{
    tr[i]=(tr[i<<1]+tr[i<<1|1])%p;
    //比如t[i] = max(t[i<<1],t[i<<1|1]);
}

void bulid(ll i,ll l,ll r)
{
    //i为当前需要建立的结点,l为当前需要建立区间的左端点,r则为右端点
    add_tag[i]=0;mul_tag[i]=1;//tag数组初始化
    if(l==r){//如果左右端点相等,即为叶子结点,直接赋值即可
        tr[i]=a[l];
        return;
    }
    ll mid=(l+r)>>1;
    bulid(i<<1,l,mid);//递归构造左子树
    bulid(i<<1|1,mid+1,r);//递归构造右子树
    pushup(i);//更新父节点,因为先是从上至下建树,再从下至上回溯,pushup相当于往上更新父节点的值。
}

inline void ADD(ll i,ll l,ll r,ll k)//[l,r]区间所有值加k
{
    add_tag[i]=(add_tag[i]+k)%p;//这么写方便取余
    tr[i]=(tr[i]+(r-l+1)*k)%p;//tr[i]是[l,r]值的和,所以加上(r-l+1)个k
}

inline void MUL(ll i,ll l,ll r,ll k)//[l,r]区间所有值乘k
{
    add_tag[i]=add_tag[i]*k%p;//加法标记也得乘,因为乘法的优先级比加法大。
    tr[i]=tr[i]*k%p;
    mul_tag[i]=mul_tag[i]*k%p;
}

inline void pushdown(ll i,ll l,ll r,ll mid)//下传懒标记到左右子树
{
    //必须先乘后加!!!
    if(mul_tag[i]==1&&(!add_tag[i])) return ;
    MUL(i<<1,l,mid,mul_tag[i]);
    MUL(i<<1|1,mid+1,r,mul_tag[i]);
    mul_tag[i]=1;

    ADD(i<<1,l,mid,add_tag[i]);
    ADD(i<<1|1,mid+1,r,add_tag[i]);
    add_tag[i]=0;
}

inline void update_ADD (ll i,ll l,ll r,ll x,ll y,ll k)//更新[x,y]区间,都加上k。
{
    //[l,r]为总区间,[x,y]为更新区间,通过不断二分递归[l,r],让l,r在[x,y]中,这样的区间就符合条件组成[x,y]
    //[x,y]是定死的,是改变l,r的值
    if(l>y||r<x) return;
    if(l>=x&&r<=y) return ADD(i,l,r,k);
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    update_ADD(i<<1,l,mid,x,y,k);
    update_ADD(i<<1|1,mid+1,r,x,y,k);
    pushup(i);
}

inline void update_MUL (ll i,ll l,ll r,ll x,ll y,ll k)//更新[x,y]区间,都乘上k。
{
    if(l>y||r<x) return;
    if(l>=x&&r<=y) return MUL(i,l,r,k);
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);//下传标记后更新左右子区间
    update_MUL(i<<1,l,mid,x,y,k);
    update_MUL(i<<1|1,mid+1,r,x,y,k);
    pushup(i);
}

ll query(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i];//符合条件时,返回下标对应的值即可。
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    if(x<=mid) res=(res+query(i<<1,l,mid,x,y))%p;
    if(y>mid)  res=(res+query(i<<1|1,mid+1,r,x,y))%p;
    return res;
}
namespace IO{
    char ibuf[1<<21],*ip=ibuf,*ip_=ibuf;
    char obuf[1<<21],*op=obuf,*op_=obuf+(1<<21);
    inline char gc(){
        if(ip!=ip_)return *ip++;
        ip=ibuf;ip_=ip+fread(ibuf,1,1<<21,stdin);
        return ip==ip_?EOF:*ip++;
    }
    inline void pc(char c){
        if(op==op_)fwrite(obuf,1,1<<21,stdout),op=obuf;
        *op++=c;
    }
    inline ll read(){
        register ll x=0,ch=gc(),w=1;
        for(;ch<'0'||ch>'9';ch=gc())if(ch=='-')w=-1;
        for(;ch>='0'&&ch<='9';ch=gc())x=x*10+ch-48;
        return w*x;
    }
    template<class I>
    inline void write(I x){
        if(x<0)pc('-'),x=-x;
        if(x>9)write(x/10);pc(x%10+'0');
    }
    class flusher_{
    public:
        ~flusher_(){if(op!=obuf)fwrite(obuf,1,op-obuf,stdout);}
    }IO_flusher;
}
using namespace IO;

int main()
{
    n=read();m=read();p=read();
    rep(i,1,n) a[i]=read();
    bulid(1,1,n);
    rep(i,1,m){
        op1=read();
        if(op1==1){
            x=read();y=read();k=read();
            update_MUL(1,1,n,x,y,k);
        }
        if(op1==2){
            x=read();y=read();k=read();
            update_ADD(1,1,n,x,y,k);
        }
        if(op1==3){
            x=read();y=read();
            printf("%lld\n",query(1,1,n,x,y));
        }
    }
    return 0;
}

小阳的贝壳

一开始看操作一,以为是区间查询和区间修改,但看了2,3操作之后又不知道从何下手。所以采取另一种方案——差分。(b[i]为原数组,a[i]为差分数组)

  • 对于操作1,就可用平时维护差分数组的基操。
  • 对于操作2,注意是从l+1开始,因为l+1的差分值是b[l+1]-b[l]。然后维护最大值的绝对值即可。
  • 对于操作3,就是我本身不会的知识点——区间gcd。先普及知识:
    g c d ( b 1 , b 2 , b 3 , . . . . . . , b n − 2 , b n − 1 , b n ) = g c d ( b 1 , ∣ b 2 − b 1 ∣ , ∣ b 3 − b 2 ∣ , . . . . . . , ∣ b n − 1 − b n − 2 ∣ , ∣ b n − b n − 1 ∣ ) gcd(b_1,b_2,b_3,......,b_{n−2},b_{n−1},b_n)=gcd(b_1,|b_2−b_1|,|b_3−b_2|,......,|b_{n−1}−b_{n−2}|,|b_n−b_{n−1}|) gcd(b1,b2,b3,......,bn2,bn1,bn)=gcd(b1,b2b1,b3b2,......,bn1bn2,bnbn1)
    自然而然,又和差分扯上关系了:
    g c d ( b l , b l + 1 , … … , b r ) = g c d ( b l , ∣ b l + 1 − b l ∣ . . . . . . , , ∣ b r − b r − 1 ∣ ) = g c d ( b l , ∣ a l + 1 ∣ , … … , ∣ a r ∣ ) = g c d ( ∑ i = 1 l a i , ∣ a l + 1 ∣ , … … , ∣ a r ∣ ) gcd(b_l,b_{l+1},……,b_r) \\ =gcd(b_l,|b_{l+1}−b_l|......,,|b_r−b_{r−1}|) \\ =gcd(b_l,|a_{l+1}|,……,|a_r|) \\ =gcd(\sum_{i=1}^l a_i,|a_{l+1}|,……,|a_r|) gcd(bl,bl+1,,br)=gcd(bl,bl+1bl......,,brbr1)=gcd(bl,al+1,,ar)=gcd(i=1lai,al+1,,ar)
    ps:第二个等号到第三个等号,是讲差分数组前缀和,就是原数组的值。

所以要维护的有,区间内 a i a_i ai的和,区间内 ∣ a i ∣ |a_i| ai的最大公约数,区间内 ∣ a i ∣ |a_i| ai 的最大值,一个单点更新,两个区间查询。
那这有三个,怎么写才方便好看呢?可以采用结构体
(最新更新单点更新+区间查询模板)

#include 
#define ls i<<1
#define rs i<<1|1
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
typedef long long ll;
using namespace std;
const int maxn=1e5+10;

struct sa{
    ll sum;//和
    ll gcd;
    ll gap;//最大值
}tr[maxn<<2];

ll a[maxn<<2],b[maxn<<2],l,r,n,m,op1,y;
inline void pushup(ll i)
{
    tr[i].sum = (tr[ls].sum+tr[rs].sum);
    tr[i].gcd = __gcd(tr[ls].gcd,tr[rs].gcd);
    tr[i].gap = max(tr[ls].gap,tr[rs].gap);
}

void bulid(ll i,ll l,ll r)
{
    if(l==r){
        tr[i].sum=a[l];
        tr[i].gcd=abs(a[l]);
        tr[i].gap=abs(a[l]);
        return;
    }
    ll mid=(l+r)>>1;
    bulid(ls,l,mid);
    bulid(rs,mid+1,r);
    pushup(i);
}

inline void update (ll i,ll l,ll r,ll x,ll y)
{
    if(l>x||r<x) return;
    if(l==x&&l==r) {
        tr[i].sum+=y;
        tr[i].gcd=abs(tr[i].sum);
        tr[i].gap=abs(tr[i].sum);
        return;
    }
    ll mid=(l+r)>>1;
    update(ls,l,mid,x,y);
    update(rs,mid+1,r,x,y);
    pushup(i);
}

ll query_sum(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i].sum;
    ll mid=(l+r)>>1;
    if(x<=mid) res=res+query_sum(ls,l,mid,x,y);
    if(y>mid)  res=res+query_sum(rs,mid+1,r,x,y);
    return res;
}

ll query_gcd(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i].gcd;
    ll mid=(l+r)>>1;
    if(x<=mid) res= __gcd(res,query_gcd(ls,l,mid,x,y));
    if(y>mid)  res= __gcd(res,query_gcd(rs,mid+1,r,x,y));
    return res;
}

ll query_gap(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i].gap;
    ll mid=(l+r)>>1;
    if(x<=mid) res= max(res,query_gap(ls,l,mid,x,y));
    if(y>mid)  res= max(res,query_gap(rs,mid+1,r,x,y));
    return res;
}

int main()
{
    cin>>n>>m;
    rep(i,1,n) cin>>b[i],a[i]=b[i]-b[i-1];
    bulid(1,1,n);//千万别忘建树啊!!!
    rep(i,1,m){
        cin>>op1>>l>>r;
        if(op1==1){
            cin>>y;
            update(1,1,n,l,y);
            if(r<n) update(1,1,n,r+1,-y);//if条件可加可不加
        }
        else if(op1==2){
            cout<<query_gap(1,1,n,l+1,r)<<endl;
        }
        else{
            cout<<abs(__gcd(query_sum(1,1,n,1,l),query_gcd(1,1,n,l+1,r)))<<endl;//加个绝对值保险一点。。
        }
    }
    return 0;
}

P1438 无聊的数列

看到等差数列,自然而然想到差分
通过举例子看看规律吧!
原 数 组 : 1   2   3   4   5 原数组:1 \ 2\ 3\ 4\ 5 :1 2 3 4 5
要在下标[2,4](l=2,r=4)上加上等差数列 a n = 2 n + 1 a_n=2n+1 an=2n+1(k=2,d=1)(n应从0开始)
新 数 组 : 1   3   6   9   5 新数组:1 \ 3\ 6\ 9\ 5 :1 3 6 9 5
原 差 分 数 组 : 1   1   1   1   1 原差分数组:1 \ 1\ 1\ 1\ 1 :1 1 1 1 1
所以
后 差 分 数 组 : 1   2   3   3   − 4 后差分数组:1 \ 2\ 3\ 3\ -4 :1 2 3 3 4
对比一下就可以看出来 (假设差分数组为a)
对 于 l   : a [ l ] = a [ l ] + k 对于l\ :a[l]=a[l]+k l :a[l]=a[l]+k
对 于 区 间 ( L , R ] : a [ i ] = a [ i ] + d , i ∈ ( L , R ] 对于区间(L,R]:a[i]=a[i]+d,i∈(L,R] (L,R]:a[i]=a[i]+d,i(L,R]
对 于 R + 1 : a [ R + 1 ] = a [ R + 1 ] − ( k + ( ( r − l ) ∗ d ) 对于R+1:a[R+1]=a[R+1]-(k+((r-l)*d) R+1:a[R+1]=a[R+1](k+((rl)d)
代码孕育而生

#include 
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll a[maxn<<2],tr[maxn<<2],add_tag[maxn<<2],    mul_tag[maxn<<2],b[maxn<<2];
//  原数组     线段树      加法lazy_tag标记    乘法lazy_tag标记
ll n,m,x,op1,l,r,d,k;

inline void pushup(ll i)//更新函数,也可以最大值,最小值。
{
    tr[i]=(tr[i<<1]+tr[i<<1|1]);
    //比如t[i] = max(t[i<<1],t[i<<1|1]);
}

void bulid(ll i,ll l,ll r)
{
    //i为当前需要建立的结点,l为当前需要建立区间的左端点,r则为右端点
    add_tag[i]=0;mul_tag[i]=1;//tag数组初始化
    if(l==r){//如果左右端点相等,即为叶子结点,直接赋值即可
        tr[i]=a[l];
        return;
    }
    ll mid=(l+r)>>1;
    bulid(i<<1,l,mid);//递归构造左子树
    bulid(i<<1|1,mid+1,r);//递归构造右子树
    pushup(i);//更新父节点,因为先是从上至下建树,再从下至上回溯,pushup相当于往上更新父节点的值。
}

inline void ADD(ll i,ll l,ll r,ll k)//[l,r]区间所有值加k
{
    add_tag[i]=(add_tag[i]+k);//这么写方便取余
    tr[i]=(tr[i]+(r-l+1)*k);//tr[i]是[l,r]值的和,所以加上(r-l+1)个k
}

inline void MUL(ll i,ll l,ll r,ll k)//[l,r]区间所有值乘k
{
    add_tag[i]=add_tag[i]*k;//加法标记也得乘,因为乘法的优先级比加法大。
    tr[i]=tr[i]*k;
    mul_tag[i]=mul_tag[i]*k;
}

inline void pushdown(ll i,ll l,ll r,ll mid)//下传懒标记到左右子树
{
    if(mul_tag[i]==1&&(!add_tag[i])) return ;
    MUL(i<<1,l,mid,mul_tag[i]);
    MUL(i<<1|1,mid+1,r,mul_tag[i]);
    mul_tag[i]=1;

    ADD(i<<1,l,mid,add_tag[i]);
    ADD(i<<1|1,mid+1,r,add_tag[i]);
    add_tag[i]=0;
}

inline void update_ADD (ll i,ll l,ll r,ll x,ll y,ll k)//更新[x,y]区间,都加上k。
{
    //[l,r]为总区间,[x,y]为更新区间,通过不断二分递归[l,r],让l,r在[x,y]中,这样的区间就符合条件组成[x,y]
    //[x,y]是定死的,是改变l,r的值
    if(l>y||r<x) return;
    if(l>=x&&r<=y) return ADD(i,l,r,k);
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    update_ADD(i<<1,l,mid,x,y,k);
    update_ADD(i<<1|1,mid+1,r,x,y,k);
    pushup(i);
}

inline void update_MUL (ll i,ll l,ll r,ll x,ll y,ll k)//更新[x,y]区间,都乘上k。
{
    if(l>y||r<x) return;
    if(l>=x&&r<=y) return MUL(i,l,r,k);
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);//下传标记后更新左右子区间
    update_MUL(i<<1,l,mid,x,y,k);
    update_MUL(i<<1|1,mid+1,r,x,y,k);
    pushup(i);
}

ll query(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i];//符合条件时,返回下标对应的值即可。
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    if(x<=mid) res=res+query(i<<1,l,mid,x,y);
    if(y>mid)  res=res+query(i<<1|1,mid+1,r,x,y);
    return res;
}
namespace IO{
    char ibuf[1<<21],*ip=ibuf,*ip_=ibuf;
    char obuf[1<<21],*op=obuf,*op_=obuf+(1<<21);
    inline char gc(){
        if(ip!=ip_)return *ip++;
        ip=ibuf;ip_=ip+fread(ibuf,1,1<<21,stdin);
        return ip==ip_?EOF:*ip++;
    }
    inline void pc(char c){
        if(op==op_)fwrite(obuf,1,1<<21,stdout),op=obuf;
        *op++=c;
    }
    inline ll read(){
        register ll x=0,ch=gc(),w=1;
        for(;ch<'0'||ch>'9';ch=gc())if(ch=='-')w=-1;
        for(;ch>='0'&&ch<='9';ch=gc())x=x*10+ch-48;
        return w*x;
    }
    template<class I>
    inline void write(I x){
        if(x<0)pc('-'),x=-x;
        if(x>9)write(x/10);pc(x%10+'0');
    }
    class flusher_{
    public:
        ~flusher_(){if(op!=obuf)fwrite(obuf,1,op-obuf,stdout);}
    }IO_flusher;
}
using namespace IO;

int main()
{
    cin>>n>>m;
    //n=read();m=read();
    rep(i,1,n) {
        cin>>b[i];
        a[i]=b[i]-b[i-1];
    }
    //b[i]=read();
    bulid(1,1,n);
    rep(i,1,m){
        cin>>op1;
        //op1=read();
        if(op1==1){
            cin>>l>>r>>k>>d;
            //l=read();r=read();k=read();d=read();
            update_ADD(1,1,n,l,l,k);
            if(r>l) update_ADD(1,1,n,l+1,r,d);
            if(r!=n) update_ADD(1,1,n,r+1,r+1,-(k+(r-l)*d));
            //如果r==n,那么差分数组最后也在维护中,就不用减去数列的末项了。上面一项已经更新完了。可以自己举个例子试试
            //其实两个if 不加也无妨
            //第二行,如果是进行的单点更新(即l==r),那么第一行就已经完成了。
            //第三行,如果r==n了,那么差分数组会多出了一个n+1项,其实数组最多n项,多出来也没人管。
        }
        if(op1==2){
            cin>>x;
            //x=read();
            printf("%lld\n",query(1,1,n,1,x));
        }
    }
    return 0;
}

P1276 校门外的树(增强版)

看代码注释即可,没用线段树,数据可能有点水吧。

#include 
using namespace std;
const int maxn=1e6+10;
int n,x,y,vis[maxn],l,r,op1,ans1,ans2;
/*
vis[i]=0 本身就有树
vis[i]=1 砍完树空了
vis[i]=2 种上树苗
vis[i]=3 砍掉树苗
*/
int main()
{
    cin>>l>>n;
    for(int i=1;i<=n;i++){
        cin>>op1>>x>>y;
        if(!op1){
            for(int i=x;i<=y;i++){
                if(!vis[i]) vis[i]=1;
                else if(vis[i]==2){
                    vis[i]=3;
                    ans2++;
                }
            }
        }
        else{
            for(int i=x;i<=y;i++){
                if(vis[i]==1) vis[i]=2;
                else if(vis[i]==3) vis[i]=2;
            }
        }
    }
    for(int i=0;i<=l;i++)
    {
        if(vis[i]==2)
            ans1++;
    }
    cout<<ans1<<endl<<ans2<<endl;
    return 0;
}

P1020 导弹拦截

没搞懂大佬线段树的意思。
100分的是 O ( n 2 ) O(n^2) O(n2)的dp
200分的得是 O ( n l o g n ) O(nlogn) O(nlogn)的算法,这里用到伟大的STL:upper_bound lower_bound
大佬的题解
注意的是这两个使用时,对象一定得是有序的数组。
如果数组是降序,那么后面得加上greater()。升序就不用加了,默认是升序。
其实就是符合就加进去,不符合就把不合适的换掉。

#include 
using namespace std;
const int maxn=1e5+10;
int a[maxn],b1[maxn],b2[maxn];
int n,len1,len2;
int main()
{
    while (cin >> a[++n]); n--;
    len1=len2=1;
    b1[1]=a[1];b2[1]=a[1];
    for(int i=2;i<=n;i++){
        if(a[i]<=b1[len1]) b1[++len1]=a[i];
        else{
            int id=upper_bound(b1+1,b1+1+len1,a[i],greater<int>())-b1;
            b1[id]=a[i];
        }
        if(a[i]>b2[len2]) b2[++len2]=a[i];
        else{
            int id=lower_bound(b2+1,b2+1+len2,a[i])-b2;
            b2[id]=a[i];
        }
    }
    cout<<len1<<endl<<len2<<endl;
    return 0;
}

NEFU2200数据区间查询-线段树

单点更新+求最小值

#include 
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
using namespace std;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f;
typedef long long ll;
ll a[maxn<<2],tr[maxn<<2];
ll n,m,t,op1,x,y,z;

inline void pushup(ll i)
{
    tr[i] = min(tr[i<<1],tr[i<<1|1]);
}

void bulid(ll i,ll l,ll r)
{
    if(l==r){
        tr[i]=a[l];
        return;
    }
    ll mid=(l+r)>>1;
    bulid(i<<1,l,mid);
    bulid(i<<1|1,mid+1,r);
    pushup(i);
}

inline void update (ll i,ll l,ll r,ll x,ll y)
{
    if(l>x||r<x) return;
    if(l==x&&l==r) {tr[i]=y;return;}//单点更新
    ll mid=(l+r)>>1;
    update(i<<1,l,mid,x,y);
    update(i<<1|1,mid+1,r,x,y);
    pushup(i);
}

ll query(ll i,ll l,ll r,ll x,ll y)
{
    ll res=inf;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i];
    ll mid=(l+r)>>1;
    if(x<=mid) res=min(res,query(i<<1,l,mid,x,y));
    if(y>mid)  res=min(res,query(i<<1|1,mid+1,r,x,y));
    return res;
}


namespace IO{
    char ibuf[1<<21],*ip=ibuf,*ip_=ibuf;
    char obuf[1<<21],*op=obuf,*op_=obuf+(1<<21);
    inline char gc(){
        if(ip!=ip_)return *ip++;
        ip=ibuf;ip_=ip+fread(ibuf,1,1<<21,stdin);
        return ip==ip_?EOF:*ip++;
    }
    inline void pc(char c){
        if(op==op_)fwrite(obuf,1,1<<21,stdout),op=obuf;
        *op++=c;
    }
    inline ll read(){
        register ll x=0,ch=gc(),w=1;
        for(;ch<'0'||ch>'9';ch=gc())if(ch=='-')w=-1;
        for(;ch>='0'&&ch<='9';ch=gc())x=x*10+ch-48;
        return w*x;
    }
    template<class I>
    inline void write(I x){
        if(x<0)pc('-'),x=-x;
        if(x>9)write(x/10);pc(x%10+'0');
    }
    class flusher_{
    public:
        ~flusher_(){if(op!=obuf)fwrite(obuf,1,op-obuf,stdout);}
    }IO_flusher;
}
using namespace IO;

int main()
{
    n=read();t=read();
    //cin>>n>>t;
    rep(i,1,n)  a[i]=read();
      //cin>>a[i];
    bulid(1,1,n);
    rep(i,1,t){
        x=read();y=read();z=read();
        //cin>>x>>y>>z;
        if(x==1){
            update(1,1,n,y,z);
        }
        if(x==2){
            printf("%lld\n",query(1,1,n,y,z));
        }
    }
    return 0;
}

NEFU1476维护序列-线段树

和洛谷模板2相同。

#include 
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll a[maxn<<2],tr[maxn<<2],add_tag[maxn<<2],    mul_tag[maxn<<2];
//  原数组     线段树      加法lazy_tag标记    乘法lazy_tag标记
ll n,m,p,op1,x,y,k;

inline void pushup(ll i)//更新函数,也可以最大值,最小值。
{
    tr[i]=(tr[i<<1]+tr[i<<1|1])%p;
    //比如t[i] = max(t[i<<1],t[i<<1|1]);
}

void bulid(ll i,ll l,ll r)
{
    //i为当前需要建立的结点,l为当前需要建立区间的左端点,r则为右端点
    add_tag[i]=0;mul_tag[i]=1;//tag数组初始化
    if(l==r){//如果左右端点相等,即为叶子结点,直接赋值即可
        tr[i]=a[l];
        return;
    }
    ll mid=(l+r)>>1;
    bulid(i<<1,l,mid);//递归构造左子树
    bulid(i<<1|1,mid+1,r);//递归构造右子树
    pushup(i);//更新父节点,因为先是从上至下建树,再从下至上回溯,pushup相当于往上更新父节点的值。
}

inline void ADD(ll i,ll l,ll r,ll k)//[l,r]区间所有值加k
{
    add_tag[i]=(add_tag[i]+k)%p;//这么写方便取余
    tr[i]=(tr[i]+(r-l+1)*k)%p;//tr[i]是[l,r]值的和,所以加上(r-l+1)个k
}

inline void MUL(ll i,ll l,ll r,ll k)//[l,r]区间所有值乘k
{
    add_tag[i]=add_tag[i]*k%p;//加法标记也得乘,因为乘法的优先级比加法大。
    tr[i]=tr[i]*k%p;
    mul_tag[i]=mul_tag[i]*k%p;
}

inline void pushdown(ll i,ll l,ll r,ll mid)//下传懒标记到左右子树
{
    //必须先乘后加!!!
    if(mul_tag[i]==1&&(!add_tag[i])) return ;
    MUL(i<<1,l,mid,mul_tag[i]);
    MUL(i<<1|1,mid+1,r,mul_tag[i]);
    mul_tag[i]=1;

    ADD(i<<1,l,mid,add_tag[i]);
    ADD(i<<1|1,mid+1,r,add_tag[i]);
    add_tag[i]=0;
}

inline void update_ADD (ll i,ll l,ll r,ll x,ll y,ll k)//更新[x,y]区间,都加上k。
{
    //[l,r]为总区间,[x,y]为更新区间,通过不断二分递归[l,r],让l,r在[x,y]中,这样的区间就符合条件组成[x,y]
    //[x,y]是定死的,是改变l,r的值
    if(l>y||r<x) return;
    if(l>=x&&r<=y) return ADD(i,l,r,k);
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    update_ADD(i<<1,l,mid,x,y,k);
    update_ADD(i<<1|1,mid+1,r,x,y,k);
    pushup(i);
}

inline void update_MUL (ll i,ll l,ll r,ll x,ll y,ll k)//更新[x,y]区间,都乘上k。
{
    if(l>y||r<x) return;
    if(l>=x&&r<=y) return MUL(i,l,r,k);
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);//下传标记后更新左右子区间
    update_MUL(i<<1,l,mid,x,y,k);
    update_MUL(i<<1|1,mid+1,r,x,y,k);
    pushup(i);
}

ll query(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i];//符合条件时,返回下标对应的值即可。
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    if(x<=mid) res=(res+query(i<<1,l,mid,x,y))%p;
    if(y>mid)  res=(res+query(i<<1|1,mid+1,r,x,y))%p;
    return res;
}
namespace IO{
    char ibuf[1<<21],*ip=ibuf,*ip_=ibuf;
    char obuf[1<<21],*op=obuf,*op_=obuf+(1<<21);
    inline char gc(){
        if(ip!=ip_)return *ip++;
        ip=ibuf;ip_=ip+fread(ibuf,1,1<<21,stdin);
        return ip==ip_?EOF:*ip++;
    }
    inline void pc(char c){
        if(op==op_)fwrite(obuf,1,1<<21,stdout),op=obuf;
        *op++=c;
    }
    inline ll read(){
        register ll x=0,ch=gc(),w=1;
        for(;ch<'0'||ch>'9';ch=gc())if(ch=='-')w=-1;
        for(;ch>='0'&&ch<='9';ch=gc())x=x*10+ch-48;
        return w*x;
    }
    template<class I>
    inline void write(I x){
        if(x<0)pc('-'),x=-x;
        if(x>9)write(x/10);pc(x%10+'0');
    }
    class flusher_{
    public:
        ~flusher_(){if(op!=obuf)fwrite(obuf,1,op-obuf,stdout);}
    }IO_flusher;
}
using namespace IO;

int main()
{
    //cin>>n>>p;
    n=read();p=read();
    rep(i,1,n) a[i]=read();
    bulid(1,1,n);
    //cin>>m;
    m=read();
    rep(i,1,m){
        //cin>>op1;
        op1=read();
        if(op1==1){
                //cin>>x>>y>>k;
            x=read();y=read();k=read();
            update_MUL(1,1,n,x,y,k);
        }
        if(op1==2){
            //cin>>x>>y>>k;
            x=read();y=read();k=read();
            update_ADD(1,1,n,x,y,k);
        }
        if(op1==3){
            //cin>>x>>y;
            x=read();y=read();
            printf("%lld\n",query(1,1,n,x,y));
        }
    }
    return 0;
}

NEFU1475花神游历各国-线段树

维护一个区间的最大值+单点修改+区间查询。
当一个区间的最大值为1时,就不用开方了。(要不然浪费时间)

#include 
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll a[maxn<<2],tr[maxn<<2],mx[maxn<<2];
ll n,m,p,op1,x,y,k;

inline void pushup(ll i)
{
    tr[i]=(tr[i<<1]+tr[i<<1|1]);
    mx[i]=max(mx[i<<1],mx[i<<1|1]);
  
}

void bulid(ll i,ll l,ll r)
{    
    if(l==r){
        tr[i]=a[l];
        mx[i]=a[l];
        return;
    }
    ll mid=(l+r)>>1;
    bulid(i<<1,l,mid);
    bulid(i<<1|1,mid+1,r);
    pushup(i);}

inline void SQRT(ll i,ll l,ll r)
{
    tr[i]=sqrt(tr[i]);
    mx[i]=tr[i];
}

inline void update_SQRT (ll i,ll l,ll r,ll x,ll y)
{
    if(mx[i]<=1) return;
    if(l>y||r<x) return;
    if(l==r) return SQRT(i,l,r);
    ll mid=(l+r)>>1;
    update_SQRT(i<<1,l,mid,x,y);
    update_SQRT(i<<1|1,mid+1,r,x,y);
    pushup(i);
}

ll query(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i];
    ll mid=(l+r)>>1;
    if(x<=mid) res=res+query(i<<1,l,mid,x,y);
    if(y>mid)  res=res+query(i<<1|1,mid+1,r,x,y);
    return res;
}


namespace IO{
    char ibuf[1<<21],*ip=ibuf,*ip_=ibuf;
    char obuf[1<<21],*op=obuf,*op_=obuf+(1<<21);
    inline char gc(){
        if(ip!=ip_)return *ip++;
        ip=ibuf;ip_=ip+fread(ibuf,1,1<<21,stdin);
        return ip==ip_?EOF:*ip++;
    }
    inline void pc(char c){
        if(op==op_)fwrite(obuf,1,1<<21,stdout),op=obuf;
        *op++=c;
    }
    inline ll read(){
        register ll x=0,ch=gc(),w=1;
        for(;ch<'0'||ch>'9';ch=gc())if(ch=='-')w=-1;
        for(;ch>='0'&&ch<='9';ch=gc())x=x*10+ch-48;
        return w*x;
    }
    template<class I>
    inline void write(I x){
        if(x<0)pc('-'),x=-x;
        if(x>9)write(x/10);pc(x%10+'0');
    }
    class flusher_{
    public:
        ~flusher_(){if(op!=obuf)fwrite(obuf,1,op-obuf,stdout);}
    }IO_flusher;
}
using namespace IO;

int main()
{
    //cin>>n;
    n=read();
    rep(i,1,n) a[i]=read();
    bulid(1,1,n);
    //cin>>m;
    m=read();
    rep(i,1,m){
        //cin>>op1;
        op1=read();
        if(op1==2){
            //cin>>x>>y;
            x=read();y=read();
            update_SQRT(1,1,n,x,y);
        }
        if(op1==1){
            //cin>>x>>y;
            x=read();y=read();
            printf("%lld\n",query(1,1,n,x,y));
        }
    }
    return 0;
}

NEFU1473A Simple Problem with Integers-线段树

同模板1

NEFU1472区间和-线段树

#include 
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
using namespace std;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f;
typedef long long ll;
ll a[maxn<<2],tr[maxn<<2];
ll n,m,t,op1,x,y,z;

inline void pushup(ll i)
{
    tr[i] = tr[i<<1]+tr[i<<1|1];
}

void bulid(ll i,ll l,ll r)
{
    if(l==r){
        tr[i]=a[l];
        return;
    }
    ll mid=(l+r)>>1;
    bulid(i<<1,l,mid);
    bulid(i<<1|1,mid+1,r);
    pushup(i);
}

inline void update (ll i,ll l,ll r,ll x,ll y)
{
    if(l>x||r<x) return;
    if(l==x&&l==r) {tr[i]+=y;return;}
    ll mid=(l+r)>>1;
    update(i<<1,l,mid,x,y);
    update(i<<1|1,mid+1,r,x,y);
    pushup(i);
}

ll query(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i];
    ll mid=(l+r)>>1;
    if(x<=mid) res=res+query(i<<1,l,mid,x,y);
    if(y>mid)  res=res+query(i<<1|1,mid+1,r,x,y);
    return res;
}


namespace IO{
    char ibuf[1<<21],*ip=ibuf,*ip_=ibuf;
    char obuf[1<<21],*op=obuf,*op_=obuf+(1<<21);
    inline char gc(){
        if(ip!=ip_)return *ip++;
        ip=ibuf;ip_=ip+fread(ibuf,1,1<<21,stdin);
        return ip==ip_?EOF:*ip++;
    }
    inline void pc(char c){
        if(op==op_)fwrite(obuf,1,1<<21,stdout),op=obuf;
        *op++=c;
    }
    inline ll read(){
        register ll x=0,ch=gc(),w=1;
        for(;ch<'0'||ch>'9';ch=gc())if(ch=='-')w=-1;
        for(;ch>='0'&&ch<='9';ch=gc())x=x*10+ch-48;
        return w*x;
    }
    template<class I>
    inline void write(I x){
        if(x<0)pc('-'),x=-x;
        if(x>9)write(x/10);pc(x%10+'0');
    }
    class flusher_{
    public:
        ~flusher_(){if(op!=obuf)fwrite(obuf,1,op-obuf,stdout);}
    }IO_flusher;
}
using namespace IO;

int main()
{
    n=read();t=read();
    //cin>>n>>t;
    bulid(1,1,n);
    rep(i,1,t){
        x=read();y=read();z=read();
        //cin>>x>>y>>z;
        if(x==0){
            update(1,1,n,y,z);
        }
        if(x==1){
            printf("%lld\n",query(1,1,n,y,z));
        }
    }
    return 0;
}

NEFU1465校门外的树3-线段树

一开始想的这题是,区间更新+找最大值。但其实是不对的。
如果在(1,3) (4,6)种树,那么查询(2,4)的最大值还是1。
正解是括号序列的思想。

用两个树状数组维护每个节点左右端点个数,端点数即是该点上覆盖的线段。
r前面的左括号数量 − - l前面的右括号数量即可。
附上一张潦草的图。
线段树入门练习_第1张图片

#include
using namespace std;
#define lowbit(x) ((x) &-(x))
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
int n,m,left_tree[1000000],right_tree[1000000],l,r,op1;

inline void add_l(int x,int d)
{
    while(x<=n){
        left_tree[x]+=d;
        x+=lowbit(x);
    }
}
inline void add_r(int x,int d)
{
    while(x<=n){
        right_tree[x]+=d;
        x+=lowbit(x);
    }
}
int sum_l(int x)
{
    int sum=0;
    while(x>0){
        sum+=left_tree[x];
        x-=lowbit(x);
    }
    return sum;
}
int sum_r(int x)
{
    int sum=0;
    while(x>0){
        sum+=right_tree[x];
        x-=lowbit(x);
    }
    return sum;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&op1,&l,&r);
		if(op1==1){
			add_l(l,1);
			add_r(r,1);
		}
		 else printf("%d\n",sum_l(r)-sum_r(l-1));
	}
}

NEFU1266快乐的雨季—线段树

卡了我一手快读,不明白怎么回事。

#include 
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll a[maxn<<2],tr[maxn<<2],add_tag[maxn<<2];
//  原数组     线段树      加法lazy_tag标记
ll n,m,p,op1,x,y,k;

inline void pushup(ll i)//更新函数,也可以最大值,最小值。
{
    tr[i]=(tr[i<<1]+tr[i<<1|1]);
    //比如t[i] = max(t[i<<1],t[i<<1|1]);
}

void bulid(ll i,ll l,ll r)//下面主函数i=1为固定的写法
{
    //i为当前需要建立的结点,l为当前需要建立区间的左端点,r则为右端点
    add_tag[i]=0;//tag数组初始化
    if(l==r){//如果左右端点相等,即为叶子结点,直接赋值即可
        tr[i]=a[l];//注意这里是l
        return;
    }
    ll mid=(l+r)>>1;
    bulid(i<<1,l,mid);//递归构造左子树
    bulid(i<<1|1,mid+1,r);//递归构造右子树
    pushup(i);//更新父节点,因为先是从上至下建树,再从下至上回溯,pushup相当于往上更新父节点的值。
}

inline void ADD(ll i,ll l,ll r,ll k)//[l,r]区间所有值加k
{
    add_tag[i]=(add_tag[i]+k);//这么写方便取余
    tr[i]=(tr[i]+(r-l+1)*k);//tr[i]是[l,r]值的和,所以加上(r-l+1)个k
}

inline void pushdown(ll i,ll l,ll r,ll mid)//下传懒标记到左右子树
{
    if((!add_tag[i])) return ;
    ADD(i<<1,l,mid,add_tag[i]);
    ADD(i<<1|1,mid+1,r,add_tag[i]);
    add_tag[i]=0; //清除标记
}

inline void update_ADD (ll i,ll l,ll r,ll x,ll y,ll k)//更新[x,y]区间,都加上k。
{
    //[l,r]为总区间,[x,y]为更新区间,通过不断二分递归[l,r],让l,r在[x,y]中,这样的区间就符合条件组成[x,y]
    //[x,y]是定死的,是改变l,r的值
    if(l>y||r<x) return;
    if(l>=x&&r<=y) return ADD(i,l,r,k);//相当于去执行这项命令
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    update_ADD(i<<1,l,mid,x,y,k);
    update_ADD(i<<1|1,mid+1,r,x,y,k);
    pushup(i);
}

ll query(ll i,ll l,ll r,ll x,ll y)
{
    ll res=0;
    if(l>y||r<x) return 0;
    if(l>=x&&r<=y) return tr[i];//符合条件时,返回下标对应的值即可。
    ll mid=(l+r)>>1;
    pushdown(i,l,r,mid);
    if(x<=mid) res=res+query(i<<1,l,mid,x,y);
    if(y>mid)  res=res+query(i<<1|1,mid+1,r,x,y);
    return res;
}

int main()
{
    while(~scanf("%lld%lld",&n,&m)){
        memset(tr,0,sizeof(tr));
        memset(add_tag,0,sizeof(add_tag));
        memset(a,0,sizeof(a));
        rep(i,1,m){
            //cin>>x>>y>>k;
            scanf("%lld%lld%lld",&x,&y,&k);
            update_ADD(1,1,n,x,y,k);
            printf("%lld\n",query(1,1,n,x,y));
        }
}
    return 0;
}

完结。

你可能感兴趣的:(ACM,线段树)