线段树维护区间最长连续0的长度

本文介绍了一种使用线段树解决区间最大连续0个数的问题,通过维护每个节点的左连续0个数、右连续0个数和是否全为0的状态,实现了区间更新和查询的功能。代码示例展示了如何在区间修改和查询操作中,有效地更新线段树的这些状态。

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

原理

本身区间最长连续0之间不可以进行区间加法操作,违背线段树基本的运算。但是容易想到两个区间合并产生的最值变化,主要由Lson的右端连续0长度加上Rson的左端连续0长度引起。即 tree[x].ma=max(tree[Lson].R0+tree[Rson].L0,max(tree[Lson].ma,tree[Rson].ma));
因此我们可以考虑维护一个区间的左端连续0个数和右端连续0个数。
但是在区间合并时我们会发现一个新的问题,即如果左区间全是0,那么tree[x].L0可以向右继续合并,tree[x].R0同理;
tree[x].L0=tree[Lson].L0+(左区间全是0?tree[Rson].L0:0);
tree[x].R0=tree[Rson].R0+(右区间全是0?tree[Rson].R0:0);

查询的时候同样需要注意不能简单返回左右区间的最大值,而是需要考虑左右区间是否存在全为零的情况;

代码

include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define L long long int
#define ls x<<1
#define rs x<<1|1
#define maxn 100005
#define maxm 1000005
int n,m;
int a[maxn];
///---------------------------------------------
///求区间最大连续0的个数  带区间修改和区间查询
struct node{
    int l0,r0,ma0;
    bool isall0;
    node(int l0,int r0,int ma0,bool isall0):l0(l0),r0(r0),ma0(ma0),isall0(isall0){};
    node(){};
};
node tree[maxn*4];
bool lazy[maxn];
void pushup(int x)
{
    tree[x].l0=tree[ls].l0+(tree[ls].isall0 ? tree[rs].l0 : 0);
    tree[x].r0=tree[rs].r0+(tree[rs].isall0 ? tree[ls].r0 : 0);
    tree[x].ma0=max(tree[ls].r0+tree[rs].l0,max(tree[ls].ma0,tree[rs].ma0));
    tree[x].isall0=tree[ls].isall0&tree[rs].isall0;
}
void pushdown(int x,int l,int r)
{
    int mid=(l+r)/2;
    tree[ls].l0=tree[ls].r0=tree[ls].ma0=mid-l+1;
    tree[rs].l0=tree[rs].r0=tree[rs].ma0=r-mid;
    tree[ls].isall0=tree[rs].isall0=1;
    lazy[ls]=lazy[rs]=1;
    lazy[x]=0;
}
void build(int x,int l,int r)
{
    if(l==r){
        tree[x].l0=tree[x].r0=tree[x].ma0=tree[x].isall0=a[l]^1;
    }
    else {
        int mid=(l+r)/2;
        build(ls,l,mid);
        build(rs,mid+1,r);
        pushup(x);
    }
}
void update(int x,int l,int r,int ll,int rr)
{
    if(lazy[x])pushdown(x,l,r);
    if(ll<=l&&r<=rr){
        tree[x].l0=tree[x].r0=tree[x].ma0=r-l+1;
        tree[x].isall0=1;
        lazy[x]=1;
        return ;
    }
    else {
        int mid=(l+r)/2;
        if(mid>=ll)update(ls,l,mid,ll,rr);
        if(mid+1<=rr)update(rs,mid+1,r,ll,rr);
        pushup(x);
    }
}
node query(int x,int l,int r,int ll,int rr)
{
    if(lazy[x])pushdown(x,l,r);
    if(ll<=l&&r<=rr)return tree[x];
    else{
        int mid=(l+r)/2;
        node now;
        now.isall0=now.l0=now.ma0=now.r0=0;
        if(rr<=mid)return query(ls,l,mid,ll,rr);
        else if(mid+1<=ll)return query(rs,mid+1,r,ll,rr);
        else if(ll<=mid&&mid+1<=rr){
            node t1=query(ls,l,mid,ll,rr);
            node t2=query(rs,mid+1,r,ll,rr);
            now.l0=t1.l0+(t1.isall0 ? t2.l0:0);
            now.r0=t2.r0+(t2.isall0 ? t1.r0:0);
            now.ma0=max(t1.r0+t2.l0,max(t1.ma0,t2.ma0));
            now.isall0=t1.isall0&t2.isall0;
            return now;
        }
    }
}
///------------------------------------------------------
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",a+i);
    }
    build(1,1,n);
    for(int i=1,x,y,z;i<=m;i++){
        scanf("%d",&x);
        if(x==1){
            scanf("%d%d",&y,&z);
            update(1,1,n,y,z);
        }
        else {
            scanf("%d%d",&y,&z);
            node ans=query(1,1,n,y,z);
            printf("l0 = %d\tr0 = %d\tma0 = %d\n",ans.l0,ans.r0,ans.ma0);
        }
    }
    return 0;
}

### 线段树优化最长上升子序列(LIS)的算法实现 最长上升子序列(Longest Increasing Subsequence, LIS)问题是动态规划中的经典问题,其朴素解法的时间复杂度为 $O(n^2)$。当数据规模较大时,这种解法效率较低。通过线段树或树状数组优化,可以将时间复杂度降低到 $O(n \log n)$,从而更高效地处理大规模数据[^2]。 #### 1. 核心思想 线段树优化 LIS 的核心思想是利用线段树维护以不同值结尾的最长上升子序列长度。在遍历数组的过程中,对于当前元素 $x$,查询线段树中所有小于 $x$ 的值所对应的最长子序列长度,然后将该值加一作为当前值的 LIS 长度,并更新线段树对应位置的值。 该方法的查询和更新操作均可以在 $O(\log n)$ 时间内完成,因此整体时间复杂度为 $O(n \log n)$。 #### 2. 线段树结构设计 线段树的每个节点表示一个值域区间,存储该区间内以任意值结尾的最长上升子序列长度线段树支持以下操作: - **区间最大值查询**:查询小于当前值的所有元素的最长子序列长度。 - **单点更新**:将当前值对应的位置更新为新的最长子序列长度。 #### 3. 实现步骤 1. **离散化处理**:由于元素值可能较大,需要对原始数组进行离散化处理,将其映射到一个较小的值域范围内。 2. **线段树构建**:初始化线段树,所有节点初始值为 0。 3. **遍历数组**:对每个元素 $x$,执行以下操作: - 查询线段树中 $[1, x-1]$ 区间的最大值。 - 当前最长上升子序列长度为查询结果加一。 - 更新线段树中 $x$ 位置的值为当前最长子序列长度。 4. **最终结果**:线段树根节点的值即为最长上升子序列的长度。 #### 4. 代码实现 以下是一个基于线段树优化的最长上升子序列算法实现: ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; struct SegmentTree { vector<int> tree; int size; SegmentTree(int n) { size = 1; while (size < n) size <<= 1; tree.assign(2 * size, 0); } void update(int pos, int value) { pos += size; tree[pos] = max(tree[pos], value); while (pos > 1) { pos >>= 1; tree[pos] = max(tree[2 * pos], tree[2 * pos + 1]); } } int query(int l, int r) { int res = 0; for (l += size, r += size; l <= r; l >>= 1, r >>= 1) { if (l % 2 == 1) res = max(res, tree[l++]); if (r % 2 == 0) res = max(res, tree[r--]); } return res; } }; int main() { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; ++i) cin >> a[i]; // 离散化 vector<int> sorted = a; sort(sorted.begin(), sorted.end()); for (int i = 0; i < n; ++i) { a[i] = lower_bound(sorted.begin(), sorted.end(), a[i]) - sorted.begin() + 1; } SegmentTree st(n); for (int i = 0; i < n; ++i) { int max_len = st.query(1, a[i] - 1); // 查询小于当前值的最大长度 st.update(a[i], max_len + 1); // 更新当前值对应的最长子序列长度 } cout << st.tree[1] << endl; // 输出最长上升子序列长度 return 0; } ``` #### 5. 性能分析 - **时间复杂度**:离散化需要 $O(n \log n)$,线段树的每次查询和更新操作为 $O(\log n)$,因此总时间复杂度为 $O(n \log n)$。 - **空间复杂度**:线段树的空间复杂度为 $O(n)$,其中 $n$ 是离散化后的值域大小。 #### 6. 应用场景 线段树优化的 LIS 算法适用于以下场景: - **大规模数据处理**:如金融数据分析、基因序列分析等,数据规模可能达到 $10^5$ 或更高。 - **实时系统**:需要快速响应的场景,例如实时推荐系统中的最长上升序列计算。 - **在线算法**:适用于数据流处理,可以在数据到来时动态更新最长上升子序列长度。 #### 7. 优化与扩展 - **支持重复元素**:可以修改线段树查询条件,使其支持非严格递增子序列。 - **多维扩展**:可将线段树扩展为二维线段树,处理二维最长上升子序列问题。 - **结合树状数组**:在某些情况下,使用树状数组实现相同功能会更简洁高效,尤其是当只需要单点更新和前缀最大值查询时[^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值