Codeforces Round #742 (Div. 2) E. Non-Decreasing Dilemma (线段树维护区间连续问题)

这篇博客介绍了如何使用线段树解决区间内数字修改和不下降序列个数查询的问题。线段树通过维护区间和、最长不下降序列长度等信息,实现了高效的更新和查询操作。在节点合并时,考虑了左右子树间序列的关系,以计算新的区间状态。代码中展示了线段树的具体实现细节,包括节点结构、更新和查询函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:
操作1:把x位置的数字修改成y。
操作2:查询[l,r]之间不下降序列的个数。
题解:
线段树维护区间和问题 (这是套路,想不到只能说做题少别打我)
用五个变量进行维护。
sum区间总个数
llen从左边这个数字开始往右最长不下降序列长度(往右端最长延伸)
rlen从右边这个数字开始往左最长不上升序列长度(往左端最长延伸)
lnum这个区间最左端的数字
rnum这个区间最右端的数字

很明显lnum和rnum很好维护,直接pushup即可。

如果左子树的右端小于等于右子树的左端。

sum则需要在两个区间进行合并时重新计算。
那么则需要减去左子树右端连续的一部分做的贡献和右子树左端连续的一部分做的贡献,加上这两个长度总共做的贡献即可(详细看代码)。

llen需要判断左子树是否为完全上升序列,如果是,那么更新llen为左子树llen+右子树llen
否则llen为左子树llen
同理
rlen需要判断右子树是否为完全上升序列,如果是,那么更新llen为右子树rlen+右子树rlen
否则rlen为左子树rlen

如果不小于等于
那么sum=左子树sum+右子树sum
llen为左子树llen
rlen为左子树rlen

代码:
之前听课听的那种维护方式在query和pushup之间不兼容,需要多写很多东西,所以换了种写法。

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int maxn=2e5+10;

int a[maxn];
//int sumlen[maxn<<2],lnum[maxn<<2],rnum[maxn<<2];
//int llen[maxn<<2],rlen[maxn<<2];
struct Node{
    int sum,llen,rlen,lnum,rnum;
}tree[maxn<<2];

inline int cal(int x){
    return (x*(x+1))/2;
}
Node mer(Node ls,Node rs,int start,int ends){
    Node res={0,0,0,ls.lnum,rs.rnum};
    int mid=(start+ends)>>1;
    if(ls.rnum<=rs.lnum){
        res.sum=ls.sum+rs.sum-cal(ls.rlen)-cal(rs.llen)+cal(ls.rlen+rs.llen);  //sum
        if(ls.llen==mid-start+1){
            res.llen=ls.llen+rs.llen;
        }
        else res.llen=ls.llen;
        if(rs.rlen==ends-mid){
            res.rlen=rs.rlen+ls.rlen;
        }
        else res.rlen=rs.rlen;
    }
    else{
        res.sum=ls.sum+rs.sum;
        res.llen=ls.llen;
        res.rlen=rs.rlen;
    }
    return res;
}

void push_up(int node,int start,int ends){
    tree[node]=mer(tree[node<<1],tree[node<<1|1],start,ends);
}
void build(int node,int start,int ends){
    if(start==ends){
        tree[node]={1,1,1,a[start],a[start]};
        return ;
    }
    int mid=start+ends>>1;
    build(node<<1,start,mid);
    build(node<<1|1,mid+1,ends);
    push_up(node,start,ends);
}
void update(int node,int start,int ends,int pos,int val){
    if(start==ends){
        tree[node].lnum=tree[node].rnum=val;
        return ;
    }
    int mid=(start+ends)>>1;
    if(pos<=mid) update(node<<1,start,mid,pos,val);
    else update(node<<1|1,mid+1,ends,pos,val);
    push_up(node,start,ends);
}


Node query(int node,int start,int ends,int l,int r){
    if(l<=start&&ends<=r){
        return tree[node];
    }
    int mid=start+ends>>1;
    Node ls,rs;
    if(l<=mid&&mid<r) return mer(query(node<<1,start,mid,l,r),query(node<<1|1,mid+1,ends,l,r),start,ends);
    else if(r<=mid) return query(node<<1,start,mid,l,r);
    else if(mid<l) return query(node<<1|1,mid+1,ends,l,r);
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,q;
    cin>>n>>q;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    build(1,1,n);
    for(int i=1;i<=q;i++){
        int opt;
        cin>>opt;
        if(opt==1){
            int x,y;
            cin>>x>>y;
            update(1,1,n,x,y);
        }
        else{
            int x,y;
            cin>>x>>y;
            Node ans=query(1,1,n,x,y);
            cout<<ans.sum<<endl;
        }
    }
    //printf("hello world" "welcome to haut");


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值