洛谷 P3372 【模板】线段树 1

题目链接:

https://www.luogu.org/problemnew/show/P3372

题意:

给你n个数,然后执行m个操作,操作分两种,对一个区间内的每个数都加上一个值,或者进行某个区间和的查询.

分析:

线段树裸题,注意lazy标记的使用;
对于lazy的个人理解,当你进行更新或者操作之时,你需要只要到你需要进行操作的区间就行,当你进行给一个区间进行加值的操作,如果没有lazy,你会递归到树的叶子节点。而使用lazy之后,你只需要到那一个或者两个包含你需要操作的区间的节点,将该节点的sum加上区间长度乘以这个你要加的值,这是更新sum,但是它的子节点,即那些小区间的sum并没有改变,于是就有了lazy,到了这一个或者两个需要更新的节点,然后并不继续向下,而是将这个需要加的值加到lazy之上,等到下一次我需要进行访问它的子节点的时候,在把它传下去;其实就是访问到刚好需要操作的区间,等待下一次操作访问到,在继续进行修改。

下面给出书上叙述:

       如果我们在一次修改指令中发现节点 p p p代表的区间[ p p p r r r, p p p l l l]被修改区间 [ l , r ] [l,r] [l,r]完全覆盖,并且逐一更新了子树 p p p中的所有节点,但是在之后的查询指令中却根本没有用到 [ l , r ] [l,r] [l,r]的子区间作为候选答案,那么更新 p p p的整颗子树就是徒劳的。
       换言之,我们在执行修改指令时,同样可以在 l &lt; = l&lt;= l<= p p p r r r &lt; = &lt;= <= p p p r r r &lt; = r &lt;=r <=r的情况下立即返回,只不过在回溯之前向节点p增加一个标记,标识 &quot; &quot; "该节点曾经被修改,但其子节点尚未被更新 &quot; &quot; "
       然后在后续的指令中,需要从节点 p p p向下递归,我们再检查 p p p是否具有标记。若有标记,就根据标记信息更新 p p p的两个子节点,同时为 p p p的两个子节点增加标记,然后清除标记。
       就是说,除了在修改指令中直接划分成的 O ( l o g N ) O(logN) O(logN)个节点之外,对任意节点的修改都延迟到 &quot; &quot; "在后续操作递归进入他的父节点时 &quot; &quot; "再执行。这样对每条查询或修改指令的时间复杂度都降低到了 O ( l o g N ) O(logN) O(logN)。这些标记即为 l a z y lazy lazy标记。
(有些题目会出现对多种对区间修改的操作,例如同时出现乘法、加法、减法,这样就不仅仅是一个 l a z y lazy lazy了)

代码:
#include<iostream>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const double inf=0x7f7f7f7f;
const int maxn=1e5+50;
const int N=2e4+50;
typedef long long ll;
typedef struct{
    int u,v,next,lca;
}Edge;
Edge e[2*maxn];

typedef struct B{
    int l,r;
    ll sum,lazy;
    void update(int value){
        sum+=(r-l+1)*value;
        lazy+=value;
    }
}Tree;
Tree tree[4*maxn];
int cnt,head[maxn];

void add(int u,int v){
    e[cnt].u=u;
    e[cnt].v=v;
    /*e[cnt].w=w;
    e[cnt].f=f;*/
    e[cnt].next=head[u];
    head[u]=cnt++;
    e[cnt].u=v;
    e[cnt].v=u;
/*  e[cnt].w=0;
    e[cnt].f=-f;*/
    e[cnt].next=head[v];
    head[v]=cnt++;
}

int read()
{
    int x = 0;
    int f = 1;
    char c = getchar();
    while (c<'0' || c>'9')
    {    
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0'&&c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x*f;
}

int n,m,x,y,k,p;


void push_up(int x){
    tree[x].sum=tree[x<<1].sum+tree[x<<1|1].sum;
}
void push_down(int x){
    tree[x<<1].update(tree[x].lazy);
    tree[x<<1|1].update(tree[x].lazy);
    tree[x].lazy=0;
}
void build(int x,int l,int r){
    tree[x].l=l,tree[x].r=r,tree[x].sum=0;
    if(l==r){
        scanf("%lld",&tree[x].sum);
        return ;
    }
    int mid=(l+r)/2;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
    push_up(x);
}

void update(int x,int l,int r,int k){
    int left=tree[x].l,right=tree[x].r;
    if(l<=left&&r>=right){
        tree[x].update(k);
        return ;
    }
    push_down(x);
    int mid=(left+right)/2;
    if(l<=mid)update(x<<1,l,r,k);
    if(r>mid)update(x<<1|1,l,r,k);
    push_up(x);
}

ll query(int x,int l,int r){
    int left=tree[x].l,right=tree[x].r;
    int mid=(left+right)/2;
    ll ans=0;
    if(l<=left&&r>=right){
        ans+=tree[x].sum;
    }
    else{
            push_down(x);
            if(l<=mid)ans+=query(x<<1,l,r);
            if(r>mid)ans+=query(x<<1|1,l,r);
            push_up(x);
    }
    return ans;
}
int main() {
    cin>>n>>m;
    build(1,1,n);
    int number;
    for(int i=0;i<m;i++){
        scanf("%d",&number);
        if(number==1){
                scanf("%d %d %d",&x,&y,&k);
                update(1,x,y,k);
                //cout<<tree[1].sum<<endl;
        }
        else if(number==2){
            scanf("%d %d",&x,&y);
            printf("%lld\n",query(1,x,y));
        }
    }
  /*  cout<<tree[1].sum<<endl;
    cout<<tree[2].sum<<endl;*/

}   

(仅供个人理解)

洛谷P1168题目是关于中位数线段树解法的问题。中位数线段树解法可以通过维护两个堆来实现。一个是大根堆,一个是小根堆。每次插入元素时,根据一定的规则来维护这两个堆,使得大根堆的个数在一定情况下比小根堆多1或者相等。大根堆的最后一个元素即为中位数。具体的规则如下: 1. 如果大根堆和小根堆的个数相等,下一次插入的元素一定插入到大根堆。此时判断小根堆的堆顶是否大于当前元素x,如果是,则将小根堆的堆顶元素插入到大根堆,然后将x压入小根堆;否则直接将x压入大根堆。 2. 如果大根堆和小根堆的个数不相等,按照类似的规则进行操作。 通过以上规则,可以实现在每次插入元素时,维护两个堆的平衡,并且保证大根堆的最后一个元素即为中位数。 这种解法的时间复杂度为O(logN),其中N为序列的长度。 #### 引用[.reference_title] - *1* *2* [中位数(洛谷p1168)(堆/树状数组+二分/线段树+二分)](https://blog.csdn.net/qq_45604735/article/details/114382762)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [洛谷 P1168 中位数(权值线段树,离散化)](https://blog.csdn.net/qq_38232157/article/details/127594230)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值