线段树之各类模板

线段树的认识,可以参考哔哔丽丽的视频。

(1)单点曽减,区间求和

飞翔

#include<stdio.h>
#define lson l,m,rt<<1///左儿子
#define rson m+1,r,rt<<1|1///右儿子
const int maxn = 55555;
int sum[maxn<<2];
void PushUP(int rt)///上一个的值由两个儿子相加
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt)///建立
{
    if(l==r)
    {
        scanf("%d",&sum[rt]);///在建立的过程中赋值
        return ;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    PushUP(rt);
}
void update(int p,int add,int l,int r,int rt)
///p点增减add
{
    if(l==r)
    {
       sum[rt]+=add;
       return ;
    }
    int m=(l+r)>>1;
    if(p<=m)
    update(p,add,lson);
    else
    update(p,add,rson);
    PushUP(rt);
}
///LR区间求和
int query(int L,int R,int l,int r,int rt)
{
    if(L <= l && r<=R)
        return sum[rt];
    int m=(l+r)>>1;
    int ret=0;
    if(L <= m )
    ret+=query(L,R,lson);
    if(R > m)
    ret+=query(L,R,rson);
    return ret;
}
int main( )
{
    int a,b,t,n;
    scanf("%d",&t);
    for(int cas=1 ; cas<=t ; cas++)
    {
        printf("Case %d:\n",cas);
        scanf("%d",&n);
        build(1,n,1);
        char op[10];
        while(scanf("%s",op))
        {
            if(op[0]=='E')
            break;
            int a,b;
            scanf("%d%d",&a,&b);
            if(op[0]=='S')
            update(a,-b,1,n,1);
            else if(op[0]=='Q')
            printf("%d\n",query(a,b,1,n,1));
            else
            update(a,b,1,n,1);
        }
    }
    return 0;
}
View Code

(2)单点替换,区间最值

飞翔

#include<stdio.h>
#include<algorithm>
using namespace std;

#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

const int manx = 222222;
int MAX[manx<<2];
void PushUP(int rt)///父亲的值由儿子来得出
{
    MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]);
}
void build(int l,int r,int rt)
{
    if ( l==r )
    {
        scanf("%d",&MAX[rt]);
        return ;
    }
    int m = (l+r) >> 1;
    build(lson);
    build(rson);
    PushUP(rt);
}
void update(int p,int sc,int l,int r,int rt)
{   
    ///p点变为sc
    if (l==r)
    {
        MAX[rt]=sc;
        return ;
    }
    int m=(l+r)>>1;
    if(p <= m )
    update(p,sc,lson);
    else
    update(p,sc,rson);
    PushUP(rt);

}
int query(int L,int R,int l,int r,int rt)
{    ///L到R的最大值
    if(L <= l&&r<= R)
        return MAX[rt];
    int m=(l+r)>>1;
    int ret=0;
    if(L<=m)
    ret=max(ret,query(L,R,lson));
    if(R>m)
    ret=max(ret,query(L,R,rson));
    return ret;
}
int main( )
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        build(1,n,1);
        while(m--)
        {
            char op[2];
            int a,b;
            scanf("%s%d%d",op,&a,&b);
            if(op[0]=='Q')
            printf("%d\n",query(a,b,1,n,1));
            else
            update(a,b,1,n,1);
        }
    }
    return 0;
}
View Code

(3)线段树功能:update:成段替换 (由于只query一次总区间,所以可以直接输出1结点的信息) ,区间覆盖

飞翔

#include <cstdio>

#include <algorithm>

using namespace std;

 

#define lson l , m , rt << 1

#define rson m + 1 , r , rt << 1 | 1

const int maxn = 111111;

int h , w , n;

int col[maxn<<2];

int sum[maxn<<2];

void PushUp(int rt) {

         sum[rt] = sum[rt<<1] + sum[rt<<1|1];

}

void PushDown(int rt,int m) {

         if (col[rt]) {

                 col[rt<<1] = col[rt<<1|1] = col[rt];

                 sum[rt<<1] = (m - (m >> 1)) * col[rt];

                 sum[rt<<1|1] = (m >> 1) * col[rt];

                 col[rt] = 0;

         }

}

void build(int l,int r,int rt) {

         col[rt] = 0;

         sum[rt] = 1;

         if (l == r) return ;

         int m = (l + r) >> 1;

         build(lson);

         build(rson);

         PushUp(rt);

}

void update(int L,int R,int c,int l,int r,int rt) {

         if (L <= l && r <= R) {

                 col[rt] = c;

                 sum[rt] = c * (r - l + 1);

                 return ;

         }

         PushDown(rt , r - l + 1);

         int m = (l + r) >> 1;

         if (L <= m) update(L , R , c , lson);

         if (R > m) update(L , R , c , rson);

         PushUp(rt);

}

int main() {

         int T , n , m;

         scanf("%d",&T);

         for (int cas = 1 ; cas <= T ; cas ++) {

                 scanf("%d%d",&n,&m);

                 build(1 , n , 1);

                 while (m --) {

                          int a , b , c;

                          scanf("%d%d%d",&a,&b,&c);

                          update(a , b , c , 1 , n , 1);

                 }

                 printf("Case %d: The total value of the hook is %d.\n",cas , sum[1]);

         }

         return 0;

}
View Code

 (4)线段树功能:update:成段增减 query:区间求和

飞翔

#include<stdio.h>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define ll long long
const int manx = 100011;
ll sum[manx<<2],la[manx<<2];
void PushUP(int rt)
{
    sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void build(int l,int r,int rt)
{
    la[rt]=0;
    if(l==r)
    {
        scanf("%lld",&sum[rt]);
        return ;
    }
    int m = (l+r) >> 1;
    build(lson);
    build(rson);
    PushUP(rt);
}
void PushDown(int rt,int m)
{
    if(la[rt])
    {
        la[rt<<1]+=la[rt];
        la[rt<<1|1]+=la[rt];
        sum[rt<<1]+=la[rt]*(m-(m>>1));
        sum[rt<<1|1]+=la[rt]*(m>>1);
        la[rt]=0;
    }
}
void update(int L,int R,int c,int l,int r,int rt)
{
    if(L<=l&&r<=R)
    {
        la[rt]+=c;
        sum[rt]+=(ll)c*(r-l+1);
        return ;
    }
    PushDown(rt,r-l+1);
    int m=(l+r)>>1;
    if(L<=m)
    update(L,R,c,lson);
    if(R>m)
    update(L,R,c,rson);
    PushUP(rt);
}
ll query(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R)
    {
        return sum[rt];
    }
    PushDown(rt,r-l+1);
    ll res=0;
    int m=(l+r)>>1;
    if(L<=m)
    res+=query(L,R,lson);
    if(R>m)
    res+=query(L,R,rson);
    return res;
}
int main( )
{
    int n,m,a,b,c;
    scanf("%d%d",&n,&m);
    build(1,n,1);
    while(m--)
    {
        getchar();
        char op[2];
        scanf("%S",&op);
        if(op[0]=='C')
        {
            scanf("%d%d%d",&a,&b,&c);
            update(a,b,c,1,n,1);
        }
        else
        {
            scanf("%d%d",&a,&b);
            ll t;
            t=query(a,b,1,n,1);
            printf("%lld\n",t);
        }

    }
}
View Code

 (5)成段覆盖,单点最大小

飞翔

#include<bits/stdc++.h>

using namespace std ;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int maxn = 200020;
int Begin[maxn << 2], End[maxn << 2];
int be[maxn],en[maxn],w[maxn];
long long sum[maxn],d[maxn];
void pushdown(int rt)//向下跟新
{
    if(!Begin[rt<<1])
    Begin[rt<<1]=Begin[rt];
    if(!Begin[rt<<1|1])
    Begin[rt<<1|1]=Begin[rt];

    if(!End[rt])
    return ;
    End[rt<<1]=End[rt<<1|1]=End[rt];
    End[rt]=0;///优化用过了就可以不用了

}
void build(int l , int r , int rt)
{
    Begin[rt]=End[rt]=0;
    if(l==r)
    return ;
    int m = (l+r) >> 1 ;
    build(lson);
    build(rson);
}

void update(int L , int R , int k , int l , int r , int rt)
{
    if(L<=l && r<=R)
    {
        if(!Begin[rt])///很简单的道理,我跟新过了就不跟新了;
        Begin[rt]=k;
        End[rt]=k;
        return ;
    }
    pushdown(rt);
    int m=(l+r) >> 1;
    if(m>=L)
    update(L,R,k,lson);
    if(m<R)
    update(L,R,k,rson);
}
void pushall(int l , int r , int rt)
{
    if(l==r)
    {
        be[l]=Begin[rt],en[l]=End[rt];
        return ;
    }
    pushdown(rt);
    int m=(l+r)>>1;
    pushall(lson);
    pushall(rson);
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        n--;
        build(1,n,1);///建树
        for(int i=1 ; i<=n ; i++)
        scanf("%d",&w[i]);

        for(int i=1 ; i<=m ; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            if(u>v)//防止意外
            swap(u,v);
            update(u,v-1,i,1,n,1);///u到v区间更新为i(天);
        }
        pushall(1,n,1);//找到每一段路的开始时间与结束时间
        memset(d,0,sizeof(d));
        for(int i=1 ; i<=n ; i++)//每一段路的开始费用与结束费用
        {
            if(be[i])
            {
                d[be[i]]+=w[i];
                d[en[i]+1]-=w[i];
            }
        }
        sum[0]=0;
        for(int i=1 ; i<=m ; i++)///类似与扫描线,一天一天的扫过去
        {
            sum[i]=sum[i-1]+d[i];
            printf("%lld\n",sum[i]);
        }
    }
}
View Code

 (6)

最大值(区间修改)
总时间限制: 10000ms 单个测试点时间限制: 1000ms 内存限制: 65536kB
描述
在N(1<=n<=100000)个数A1…An组成的序列上进行M(1<=m<=100000)次操作,操作有两种:
(1)1 LR C:表示把A[L]到A[R]增加C(C的绝对值不超过10000);
(2)2 LR:询问A[L]到A[R]之间的最大值。

输入
第一行输入N(1<=N<=100000),表示序列的长度,接下来N行输入原始序列;接下来一行输入M(1<=M<=100000)表示操作的次数,接下来M行,每行为1 L R C或2 L R

输出
对于每个操作(2)输出对应的答案。

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
using std::cin;
using std::cout;
using std::endl;

const int maxn=100005;
int tree[maxn*3];
int lazy[maxn*3];
int n,m;

void build(int node=1, int l=1, int r=n)
{
    if(l==r)
    {
        scanf("%d",&tree[node]);
        return;
    }
    int mid=(l+r)/2;
    build(node<<1, l, mid);
    build((node<<1)+1, mid+1, r);
    tree[node]=std::max(tree[node<<1], tree[(node<<1)+1]);
}

void pushdown(int node)
{
    if(lazy[node])
    {
        lazy[node<<1]+=lazy[node];
        lazy[(node<<1)+1]+=lazy[node];
        tree[node<<1]+=lazy[node];
        tree[(node<<1)+1]+=lazy[node];
        lazy[node]=0;
    }
}

int g_L,g_R,g_Add;
void change(int node=1, int l=1, int r=n)
{
    if(g_L<=l && r<=g_R)
    {
        tree[node]+=g_Add; //这个结点对应线段的所有点都加上了g_Add,所以最大值也加g_Add 
        lazy[node]+=g_Add; //我们只操作这个结点,而不递归传下去,因为这时我们传下去了也用不到,所以通过lazy保存结点对应线段每个点的增加值 
        return;
    }
    int mid=(l+r)/2;
    int lc=node<<1;
    int rc=(node<<1)+1;
    //现在要更新子结点了对吧,既然子结点的最大值还没有加上g_Add,那我们怎么知道加了后的值是多少呢? 
    pushdown(node); //那就更新它,把lazy记号推下去 
    if(g_L<=mid)
        change(lc, l, mid);
    if(g_R>mid)
        change(rc, mid+1, r);
    tree[node]=std::max(tree[lc],tree[rc]); //记住要回来更新父结点 
}

//使用g_L和g_R
int query(int node=1, int l=1, int r=n)
{
    if(g_L<=l && r<=g_R)
    {
        return tree[node]; //注意tree[node]的含义:我们已经保证tree[node]已经更新,所以答案就是tree[node],不要再加上lazy[node],它是作用于子结点的 
    }
    int mid=(l+r)/2;
    int lc=node<<1;
    int rc=(node<<1)+1;
    pushdown(node); //查询时也要更新,以把加上的值记录在内 

    int ans=0x80000000;
    if(g_L<=mid)
        ans=std::max(ans, query(lc, l, mid));
    if(g_R>mid)
        ans=std::max(ans, query(rc, mid+1, r));
    return ans;
}

int main()
{
    scanf("%d",&n);
    build();
    scanf("%d",&m);
    while(m--)
    {
        int operation, l, r, value;
        scanf("%d%d%d", &operation, &l, &r);

        if(operation==1)
        {
            scanf("%d", &value);
            g_L=l;
            g_R=r;
            g_Add=value;
            change();
        }
        else if(operation==2)
        {
            g_L=l;
            g_R=r;
            printf("%d\n",query());
        }
    }
    return 0;
}
View Code

 

(7)区间修改的总模板

<pre name="code" class="cpp">#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>
#include <math.h>
#include <queue>
#define MAXN 100010
#define inf 0x3f3f3f3f
 
using namespace std;
 
struct node{
    int l,r;//区间[l,r]
    int add;//区间的延时标记
    int sum;//区间和
    int mx; //区间最大值
    int mn; //区间最小值
}tree[MAXN<<2];//一定要开到4倍多的空间
 
void pushup(int index){
    tree[index].sum = tree[index<<1].sum+tree[index<<1|1].sum;
    tree[index].mx = max(tree[index<<1].mx,tree[index<<1|1].mx);
    tree[index].mn = min(tree[index<<1].mn,tree[index<<1|1].mn);
}
void pushdown(int index){
    //说明该区间之前更新过
    //要想更新该区间下面的子区间,就要把上次更新该区间的值向下更新
    if(tree[index].add > 0){
        //替换原来的值
        /*
        tree[index<<1].sum = (tree[index<<1].r-tree[index<<1].l+1)*tree[index].add;
        tree[index<<1|1].sum = (tree[index<<1|1].r-tree[index<<1|1].l+1)*tree[index].add;
        tree[index<<1].mx = tree[index].add;
        tree[index<<1|1].mx = tree[index].add;
        tree[index<<1].mn = tree[index].add;
        tree[index<<1|1].mn = tree[index].add;
        tree[index<<1].add = tree[index].add;
        tree[index<<1|1].add = tree[index].add;
        tree[index].add = 0;*/
        //在原来的值的基础上加上val
        
        tree[index<<1].sum += (tree[index<<1].r-tree[index<<1].l+1)*tree[index].add;
        tree[index<<1|1].sum +=(tree[index<<1|1].r-tree[index<<1|1].l+1)*tree[index].add;
        tree[index<<1].mx += tree[index].add;
        tree[index<<1|1].mx += tree[index].add;
        tree[index<<1].mn += tree[index].add;
        tree[index<<1|1].mn += tree[index].add;
        tree[index<<1].add += tree[index].add;
        tree[index<<1|1].add += tree[index].add;
        tree[index].add = 0;
 
    }
}
void build(int l,int r,int index){
    tree[index].l = l;
    tree[index].r = r;
    tree[index].add = 0;//刚开始一定要清0
    if(l == r){
        scanf("%d",&tree[index].sum);
        tree[index].mn = tree[index].mx = tree[index].sum;
        return ;
    }
    int mid = (l+r)>>1;
    build(l,mid,index<<1);
    build(mid+1,r,index<<1|1);
    pushup(index);
}
void updata(int l,int r,int index,int val){
    if(l <= tree[index].l && r >= tree[index].r){
        /*把原来的值替换成val,因为该区间有tree[index].r-tree[index].l+1
        个数,所以区间和 以及 最值为:
        */
        /*tree[index].sum = (tree[index].r-tree[index].l+1)*val;
        tree[index].mn = val;
        tree[index].mx = val;
        tree[index].add = val;//延时标记*/
        //在原来的值的基础上加上val,因为该区间有tree[index].r-tree[index].l+1
        //个数,所以区间和 以及 最值为:
        tree[index].sum += (tree[index].r-tree[index].l+1)*val;
        tree[index].mn += val;
        tree[index].mx += val;
        tree[index].add += val;//延时标记
 
        return ;
    }
    pushdown(index);
    int mid = (tree[index].l+tree[index].r)>>1;
    if(l <= mid){
        updata(l,r,index<<1,val);
    }
    if(r > mid){
        updata(l,r,index<<1|1,val);
    }
    pushup(index);
}
int query(int l,int r,int index){
    if(l <= tree[index].l && r >= tree[index].r){
        //return tree[index].sum;
        return tree[index].mx;
        //return tree[index].mn;
    }
    pushdown(index);
    int mid = (tree[index].l+tree[index].r)>>1;
    int ans = 0;
    int Max = 0;
    int Min = inf;
    if(l <= mid){
        ans += query(l,r,index<<1);
        Max = max(query(l,r,index<<1),Max);
        Min = min(query(l,r,index<<1),Min);
    }
    if(r > mid){
        ans += query(l,r,index<<1|1);
        Max = max(query(l,r,index<<1|1),Max);
        Min = min(query(l,r,index<<1|1),Min);
    }
    //return ans;
    return Max;
    //return Min;
}
int main()
{
    int n,m,q,x,y,z;
    while(~scanf("%d%d",&n,&m)){
        build(1,n,1);
        while(m--){
            scanf("%d",&q);
            if(q == 1){
                cout<<"查询:(x,y)"<<endl;
                scanf("%d %d",&x,&y);
                cout<<query(x,y,1)<<endl;
            }
            else{
                cout<<"更新(x,y)为z:"<<endl;
                scanf("%d %d %d",&x,&y,&z);
                updata(x,y,1,z);
                for(int i = 1; i <= n; ++i){
                    printf("a[%d] = %d\n",i,query(i,i,1));
                }
            }
        }
    }
    return 0;
}
View Code

 (8)区间修改区间整除

题意

初始a数组为0,给你一个全排列的b数组,q次询问add x y为a数组区间x y增加1,query x y查询a数组整除b数组对应下标的和

题解

区间操作很容易想到线段树

初始每个叶子节点赋值为b[i],维护一个区间最小值min,和区间和sum

对于每个add,区间[X,Y]最小值减1,如果当前区间最小值=1,就继续往下更新,如果更新到叶子节点并且min=1,sum+1

对于每个query,查询区间[X,Y]sum,如果区间min=0,再去暴力更新区间(可以知道一共q次询问,q/1+q/2+q/3+....q/n为调和级数,复杂度O(logn))

总复杂度O(nlog^2 n)

 

    这个题一眼望过去,区间更新,区间求和,很明显就可以想到用线段树进行维护。

    但是这个问题的难点在于这个式子 c[i]=ai/bi的求和相对来说不好直接处理。在这里,我们需要对这个式子进行分析。我们需要发现,对于每一个c[i],它的值倘若能够成功加1,那么必定是ai在区间更新中增加了bi 。因此我们就可以往这个方向去考虑。

    但是对ai不断的加1并判断与bi的关系,处理起来相对比较麻烦,因此我们就可以逆向考虑:使得在每次更新中bi-1,而若bi减到0,则使答案+1,并使得bi重新变为原值。

    如此一来,我们只需要用线段树对区间bi的最小值min以及答案c[i]进行维护即可

#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
typedef long long ll;
struct Tree{
    ll sum,valb,minn,add;
}tr[maxn<<2];
int n,q;
int a[maxn],b[maxn];
void push_up(int rt){//维护区间bi的最小值以及答案
    tr[rt].minn=min(tr[rt<<1].minn,tr[rt<<1|1].minn);
    tr[rt].sum=tr[rt<<1].sum+tr[rt<<1|1].sum;
}
void push_down(int rt){//lazy操作
    if(tr[rt].add){
        tr[rt<<1].add+=tr[rt].add;
        tr[rt<<1|1].add+=tr[rt].add;
        tr[rt<<1].minn-=tr[rt].add;
        tr[rt<<1|1].minn-=tr[rt].add;
        tr[rt].add=0;
    }
}
void build(int l,int r,int rt){//建树
    tr[rt].add=tr[rt].sum=0;
    if(l==r){
        tr[rt].minn=tr[rt].valb=b[l];
        tr[rt].sum=0;
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    push_up(rt);
}
void update(int L,int R,int l,int r,int rt){//区间更新
    //如果询问区间[L,R]能被覆盖且[L,R]的最小值大于0,则进行-1操作
    if(L<=l&&R>=r&&tr[rt].minn>1){
        tr[rt].add++;
        tr[rt].minn--;
        return;
    }
    //否则不断遍历到叶子结点并判断当前bi的值是否为0
    if(l==r&&tr[rt].minn==1){
        tr[rt].sum++;
        tr[rt].minn=tr[rt].valb;
        tr[rt].add=0;
        return;
    }
    int mid=(l+r)>>1;
    push_down(rt);
    if(L<=mid) update(L,R,l,mid,rt<<1);
    if(R>mid) update(L,R,mid+1,r,rt<<1|1);
    push_up(rt);
}
ll query(int L,int R,int l,int r,int rt){//区间求和
    if(L<=l&&R>=r){
        return tr[rt].sum;
    }
    int mid=(l+r)>>1;
    push_down(rt);
    ll ans=0;
    if(L<=mid) ans+=query(L,R,l,mid,rt<<1);
    if(R>mid) ans+=query(L,R,mid+1,r,rt<<1|1);
    return ans;
}
int main()
{
    while(~scanf("%d%d",&n,&q)){
        for(int i=1;i<=n;i++){
            scanf("%d",&b[i]);
        }
        build(1,n,1);
        while(q--){
            string str;
            cin>>str;
            int l,r;
            scanf("%d%d",&l,&r);
            if(str[0]=='a'){
                update(l,r,1,n,1);
            }
            else{
                ll res=query(l,r,1,n,1);
                printf("%lld\n",res);
            }
        }
    }
    return 0;
}
View Code

 

感激这位大牛的模板

posted @ 2018-05-21 19:58  shuai_hui  阅读(299)  评论(0编辑  收藏  举报