zoukankan      html  css  js  c++  java
  • 线段树

    线段树板子

    线段树最原始的功能是区间求和

    因为是树状结构,使得查询和修改的复杂度都为O(log n)级别

    又因为有懒标记的存在,大大降低了复杂度

    所以我觉得,树状结构式线段树的基本

    懒标记是线段树的灵魂

    #include<iostream>
    using namespace std;
    #define ll long long
    #define lid (id << 1)
    #define rid (id << 1) | 1//位运算可以降低常数
    const ll maxn = 100110;
    ll a[maxn];
    struct sag_tree{ll l,r;ll lazy;ll sum;}tree[maxn << 2];
    
    void build(ll id,ll l,ll r){
        tree[id].l = l;
        tree[id].r = r;
        if(l == r){
            tree[id].sum = a[l];
            return;//记得return
            }
        ll mid = (l + r) >> 1;
        build(lid,l,mid);
        build(rid,mid + 1,r);
        tree[id].sum = tree[lid].sum + tree[rid].sum;
        }
    
    void pushdown(ll id){//懒标记下放(pushdown(i)是更新儿子而不是自己)
        if(tree[id].lazy != 0 && tree[id].l != tree[id].r){
            ll val = tree[id].lazy;
            tree[lid].lazy += val;
            tree[rid].lazy += val;
            tree[lid].sum += val * (tree[lid].r - tree[lid].l + 1);
            tree[rid].sum += val * (tree[rid].r - tree[rid].l + 1);
            tree[id].lazy = 0;//记得归零
            }
        }
    
    void update(ll id,ll val,ll l,ll r){
        pushdown(id);
        if(tree[id].l == l && tree[id].r == r){
            tree[id].lazy += val;
            tree[id].sum += val * (r - l + 1);
            return;
            }
        ll mid = (tree[id].l + tree[id].r) >> 1;
        if(mid < l){
            update(rid,val,l,r);
            }
        else if(mid >= r){
            update(lid,val,l,r);
            }
        else{
            update(lid,val,l,mid);
            update(rid,val,mid + 1,r);
            }
        tree[id].sum = tree[lid].sum + tree[rid].sum;
        }
        
    ll getsum(ll id,ll l,ll r){
        pushdown(id);
        if(tree[id].l == l && tree[id].r == r){
            return tree[id].sum;
            }
        ll mid = (tree[id].l + tree[id].r) >> 1;
        if(mid < l){
            return getsum(rid,l,r);
            }
        else if(mid >= r){
            return getsum(lid,l,r);
            }
        else{
            return getsum(lid,l,mid) + getsum(rid,mid + 1,r);
            }
        }
        
    int main(){
        ll num,nask;
        cin>>num>>nask;
        for(ll i = 1;i <= num;i++)cin>>a[i];
        build(1,1,num);
        ll ask,x,y,v;
        for(ll i = 1;i <= nask;i++){
            cin>>ask;
            if(ask == 1){
                cin>>x>>y>>v;
                update(1,v,x,y);
                }
            if(ask == 2){
                cin>>x>>y;
                cout<<getsum(1,x,y)<<endl;
                }
            }
        return 0;
        }
    

    还有就是既涉及乘法又涉及加法是要有一个优先级的概念

    #include<iostream>
    #include<cstdio>
    using namespace std;
    long long RD(){
        int out = 0,flag = 1;char c = getchar();
        while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
        while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
        return flag * out;
        }
    #define lid (id << 1)
    #define rid (id << 1) | 1
    #define ll long long
    const long long maxn = 100100;
    long long num,nask,P;
    long long a[maxn];
    struct sag_tree{long long l,r;long long sum,add,mul;}tree[maxn << 2];
    void build(long long id,long long l,long long r){
        tree[id].add = 0;
        tree[id].mul = 1;
        tree[id].l = l;
        tree[id].r = r;
        if(l == r){
            tree[id].sum = a[l];
            return ;
            }
        long long mid = (l + r) >> 1;
        build(lid,l,mid);
        build(rid,mid + 1,r);
        tree[id].sum = (tree[lid].sum + tree[rid].sum) % P;
        }
    
    void pushdown(long long id){
        if((tree[id].add != 0 || tree[id].mul != 1) && tree[id].l != tree[id].r){
            long long add = tree[id].add;
            long long mul = tree[id].mul;
            tree[lid].sum *= mul;//乘法
            tree[lid].sum %= P;
            tree[lid].sum += (tree[lid].r - tree[lid].l + 1) * add;//加法
            tree[lid].sum %= P;
            
            tree[rid].sum *= mul;
            tree[rid].sum %= P;
            tree[rid].sum += (tree[rid].r - tree[rid].l + 1) * add;
            tree[rid].sum %= P;//更新左右儿子
            
            tree[lid].mul *= mul;
            tree[lid].mul %= P;
            tree[lid].add = (tree[lid].add * mul + tree[id].add) % P;
            
            tree[rid].mul *= mul;
            tree[rid].mul %= P;
            tree[rid].add = (tree[rid].add * mul + tree[id].add) % P;
            
            tree[id].add = 0;
            tree[id].mul = 1;
            }
        }
        
    void update1(long long id,long long val,long long l,long long r){
        pushdown(id);
        if(tree[id].l == l && tree[id].r == r){
            tree[id].sum *= val;
            tree[id].sum %= P;
            tree[id].mul *= val;
            tree[id].mul %= P;
            tree[id].add *= val;
            tree[id].add %= P;
            return ;
            }
        long long mid = (tree[id].l + tree[id].r) >> 1;
        if(mid < l){
            update1(rid,val,l,r);
            }
        else if(mid >= r){
            update1(lid,val,l,r);
            }
        else{
            update1(lid,val,l,mid);
            update1(rid,val,mid + 1,r);
            }
        tree[id].sum = (tree[lid].sum + tree[rid].sum) % P;
        }
        
    void update2(long long id,long long val,long long l,long long r){
        
        pushdown(id);
        if(tree[id].l == l && tree[id].r == r){
            tree[id].sum += (tree[id].r - tree[id].l + 1) * val;
            tree[id].sum %= P;
            tree[id].add += val;
            tree[id].add %= P;
            return ;
            }
        long long mid = (tree[id].l + tree[id].r) >> 1;
        if(mid < l){
            update2(rid,val,l,r);
            }
        else if(mid >= r){
            update2(lid,val,l,r);
            }
        else{
            update2(lid,val,l,mid);
            update2(rid,val,mid + 1,r);
            }
        tree[id].sum = (tree[lid].sum + tree[rid].sum) % P;
        }
    long long query(long long id,long long l,long long r){
        pushdown(id);
        if(tree[id].l == l && tree[id].r == r){
            return tree[id].sum;
            }
        long long mid = (tree[id].l + tree[id].r) >> 1;
        if(mid < l){
            return query(rid,l,r);
            }
        else if(mid >= r){
            return query(lid,l,r);
            }
        else{
            return (query(lid,l,mid) + query(rid,mid + 1,r)) % P;
            }
        }
    int main(){
        num = RD();nask = RD();P = RD();
        for(long long i = 1;i <= num;i++)a[i] = RD();
        build(1,1,num);
        long long ask,l,r,val;
        for(long long i = 1;i <= nask;i++){
            ask = RD();l = RD();r = RD();
            if(ask == 1){
                val = RD();
                update1(1,val,l,r);
                }
            else if(ask == 2){
                val = RD();
                update2(1,val,l,r);
                }
            else{
                printf("%d
    ",query(1,l,r));
                }
            }
        return 0;
        }
    

    线段树的其他用法

    我们可以人为的定义线段树的概念【在建树里得到体现】,对修改及查询函数做适当的修改,从而解决很多问题

    很基础的例子就是查询一个区间内的最值:

    P1531 I Hate It

    题目背景

    很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。这让很多学生很反感。

    题目描述

    不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩

    输入输出格式

    输入格式:
    第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。学生ID编号分别从1编到N。第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。接下来有M行。每一行有一个字符 C (只取'Q'或'U') ,和两个正整数A,B。当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。当C为'U'的时候,表示这是一条更新操作,如果当前A学生的成绩低于B,则把ID为A的学生的成绩更改为B,否则不改动。

    输出格式:
    对于每一次询问操作,在一行里面输出最高成绩


    典型的最大值查询问题。因为涉及分数修改,所以我们用线段树解决问题

    我们定义一个线段树的max表示其管理区间内的最大值,建树时不更新为和,而是更新为最大值(修改定义)

    对于更新函数,我们在修改后回溯时同样返回区间最大值

    对于询问函数,若刚好为这个区间,则直接返回这个区间的max,若横跨区间,则找出这几个区间内的max,返回

    #include<iostream>
    #include<algorithm>
    using namespace std;
    #define lid (id << 1)
    #define rid (id << 1) | 1
    const int maxn = 200100;
    int num,na;
    
    int a[maxn];
    struct sag_tree{int l,r,sum;}tree[maxn << 2];
    
    void build(int id,int l,int r){
        tree[id].l = l;
        tree[id].r = r;
        if(l == r){
            tree[id].sum = a[l];
            return;
            }
        int mid = (l + r) >> 1;
        build(lid,l,mid);
        build(rid,mid + 1,r);
        tree[id].sum = max(tree[lid].sum,tree[rid].sum);//修改定义
        }
    void update(int id,int p,int l,int r){
        if(tree[id].l == l && tree[id].r == r){
            tree[id].sum = max(tree[id].sum,p);
            return;
            }
        int mid = (tree[id].l + tree[id].r) >> 1;
        if(mid < l){
            update(rid,p,l,r);
            }
        else if(mid >= r){
            update(lid,p,l,r);
            }
        tree[id].sum = max(tree[lid].sum,tree[rid].sum);//回溯维护区间最大值
        }
    int query(int id,int l,int r){
        if(tree[id].l == l && tree[id].r == r){
            return tree[id].sum;
            }
        int mid = (tree[id].l + tree[id].r) >> 1;
        if(mid < l){
            return query(rid,l,r);
            }
        else if(mid >= r){
            return query(lid,l,r);
            }
        else{
            return max(query(lid,l,mid),query(rid,mid + 1,r));
            }
        }
    int main(){
        cin>>num>>na;
        for(int i = 1;i <= num;i++)cin>>a[i];
        build(1,1,num);
        char ask;
        int a,b;
        for(int i = 1;i <= na;i++){
            cin>>ask>>a>>b;
            if(ask == 'Q'){
                cout<<query(1,a,b)<<endl;
                }
            else{
                update(1,b,a,a);
                }
            }
        return 0;
        }
    

    P2826 [USACO08NOV]光开关Light Switching

    题目描述

    灯是由高科技——外星人鼠标操控的。你只要左击两个灯所连的鼠标,

    这两个灯,以及之间的灯都会由暗变亮,或由亮变暗。右击两个灯所连的鼠

    标,你就可以知道这两个灯,以及之间的灯有多少灯是亮的。起初所有灯都是暗的,你的任务是在LZ之前算出灯的亮灭。

    输入输出格式

    输入格式:
    第1 行: 用空格隔开的两个整数N 和M,n 是灯数

    第2..M+1 行: 每行表示一个操作, 有三个用空格分开的整数: 指令号, S_i 和E_i

    第1 种指令(用0 表示)包含两个数字S_i 和E_i (1 <= S_i <= E_i <= N), 它们表示起

    始开关和终止开关. 表示左击

    第2 种指令(用1 表示)同样包含两个数字S_i 和E_i (1 <= S_i <= E_i <= N), 不过这

    种指令是询问从S_i 到E_i 之间的灯有多少是亮着的.


    同样的

    我们定义一个on,on == 1为亮;on == 0为暗

    区间修改:区间内 1 -> 0 或 0 -> 1

    查询:直接求和即可

    懒标记下放:(因为题意,这题是可以标记下放的,所以)我们规定lazy == 1 表示儿子需要转换状态,lazy == 0 表示不需要,若更新时有lazy == 1,直接改为0即可

    #include<iostream>
    using namespace std;
    #define ll long long
    #define lid (id << 1)
    #define rid (id << 1) | 1
    const ll maxn = 100110;
    ll a[maxn];
    struct sag_tree{ll l,r;ll lazy;ll sum;}tree[maxn << 2];
    
    void build(ll id,ll l,ll r){
        tree[id].l = l;
        tree[id].r = r;
        if(l == r){
            tree[id].sum = a[l];
            return;
            }
        ll mid = (l + r) >> 1;
        build(lid,l,mid);
        build(rid,mid + 1,r);
        tree[id].sum = tree[lid].sum + tree[rid].sum;
        }
    
    void pushdown(ll id){
        if(tree[id].lazy == 1 && tree[id].l != tree[id].r){
            tree[lid].lazy = 1 - tree[lid].lazy;
            tree[rid].lazy = 1 - tree[rid].lazy;
            tree[lid].sum = (tree[lid].r - tree[lid].l + 1) - tree[lid].sum;
            tree[rid].sum = (tree[rid].r - tree[rid].l + 1) - tree[rid].sum;
            tree[id].lazy = 0;
            }
        }
    
    void update(ll id,ll l,ll r){
        pushdown(id);
        if(tree[id].l == l && tree[id].r == r){
            tree[id].lazy = 1 - tree[id].lazy;
            
            tree[id].sum = (tree[id].r - tree[id].l + 1) - tree[id].sum;
            return;
            }
        ll mid = (tree[id].l + tree[id].r) >> 1;
        if(mid < l){
            update(rid,l,r);
            }
        else if(mid >= r){
            update(lid,l,r);
            }
        else{
            update(lid,l,mid);
            update(rid,mid + 1,r);
            }
        tree[id].sum = tree[lid].sum + tree[rid].sum;
        }
        
    ll getsum(ll id,ll l,ll r){
        pushdown(id);
        if(tree[id].l == l && tree[id].r == r){
            return tree[id].sum;
            }
        ll mid = (tree[id].l + tree[id].r) >> 1;
        if(mid < l){
            return getsum(rid,l,r);
            }
        else if(mid >= r){
            return getsum(lid,l,r);
            }
        else{
            return getsum(lid,l,mid) + getsum(rid,mid + 1,r);
            }
        }
        
    int main(){
        ll num,nask;
        cin>>num>>nask;
        build(1,1,num);
        ll ask,x,y,v;
        for(ll i = 1;i <= nask;i++){
            cin>>ask;
            if(ask == 0){
                cin>>x>>y;
                update(1,x,y);
                }
            if(ask == 1){
                cin>>x>>y;
                cout<<getsum(1,x,y)<<endl;
                }
            }
        return 0;
        }
    
  • 相关阅读:
    LeetCode 1275. 找出井字棋的获胜者 Find Winner on a Tic Tac Toe Game
    LeetCode 307. 区域和检索
    LeetCode 1271 十六进制魔术数字 Hexspeak
    秋实大哥与花 线段树模板
    AcWing 835. Trie字符串统计
    Leetcode 216. 组合总和 III
    Mybatis 示例之 复杂(complex)属性(property)
    Mybatis 示例之 复杂(complex)属性(property)
    Mybatis 高级结果映射 ResultMap Association Collection
    Mybatis 高级结果映射 ResultMap Association Collection
  • 原文地址:https://www.cnblogs.com/Tony-Double-Sky/p/9283231.html
Copyright © 2011-2022 走看看