如何删除给定索引之间的所有元素?

时间:2018-12-29 20:51:09

标签: c++ algorithm

简要介绍问题的情况:

给出从1到n的数字,并进行m个纯化阶段,然后在m行中左右两个数字(边界,包括边界),删除数字的范围(1..n),必须输出去除后的所有生物。

我举一个例子:

n = 10, m = 3

假设我们创建一个数组a [1,2,3,4,5,6,7,8,9,10];

left = 1, right = 2;

删除1次后:a [3,4,5,6,7,8,9,10];

left = 4, right = 5;

删除2后:a [3,4,5,8,9,10];

left = 3, right = 5;

删除3后:a [3,4,10];

Conclusion: 3 4 10

不是所有的事情都那么简单,限制是严格的,即:

n, m <= 3 * 10 ^ 5
left <= right

我的尝试如下:我创建了一个从1到n的数字的向量,并删除了[left,right]范围内的所有元素,但是由于复杂性,时间限制来了。

#include <iostream>
#include <vector>
using namespace std;
#define ll uint64_t
int main() {
    ll i, n, k, l, r;
    cin >> n >> k;
    vector <ll> a;
    for (i = 1; i <= n; i++) {
        a.push_back(i);
    }   
    for (i = 1; i <= k; i++) {
        cin >> l >> r;
        a.erase(a.begin()+l-1,a.begin()+r);
    }
    cout << a.size() << endl;
    for (auto i : a) {
        cout << i << ' ';
    }
}

如何解决这个问题?

1 个答案:

答案 0 :(得分:2)

使用具有延迟传播和O((N + Q)* log(N))中的顺序统计量的分段树,可以解决此问题。在给定您的约束的情况下,大多数在线法官都应该在一两秒钟内通过该树。

简要说明

“仍然存在” 布尔数组

让我们想象一下,我们有一个大小为 N 的布尔数组,该数组指示每个项目是否仍然存在或已删除。由于尚未删除任何元素,因此将使用1初始化该数组。

细分树

信息查询:让我们在此布尔数组的顶部构建一个范围和段树(True映射为1,false映射为0)。如果我们查询任何[L,R]范围,则​​细分树将以仍然存在的元素数进行回答。 (请注意,L和R是原始数组中的索引-包括已删除和未删除的元素-)

更新查询:在段树上进行的唯一更新查询是将范围设置为零(将元素范围标记为已删除)。由于我们将元素范围更新为零,因此我们需要使用延迟传播(如果问题需要删除单个项目,则不需要它)。

最终输出

更新所有给定零的范围后,我们可以遍历每个索引并检查它是否为零或一,并打印它是否为一,但是解决方案并不那么容易,因为输入中提供的范围不是范围内的原始数组,实际上是更新后的数组中的索引。

更新范围问题

要进一步了解该问题,我们来看一个示例:

假设我们正在使用长度为6的数组,该数组最初为: 1 2 3 4 5 6 ,而布尔数组最初为: 1 1 1 1 1 1

我们假设第一个删除为[2,4],现在新数组为: 1 5 6 ,而新的更新后的布尔数组为: 1 0 0 0 1 1 < / strong>

这时,如果要求我们打印数组,我们将简单遍历原始数组并打印仅对应于布尔数组中true的值。

现在让我们尝试删除另一个范围[1,2],如果我们仅将前两个元素设置为零,那么我们将得到: 0 0 0 0 1 1 。这意味着我们在数组中仍然有 5,6 ,而实际上在最后一次删除之后我们只有 6

订单统计信息来解决范围更新问题

要解决该问题,我们需要将order-statistics属性添加到我们的细分树中。此属性将回答以下问题:给出 X ,找到索引是以它结尾的索引的前缀和为 X 帮助我们将当前的[L,R]映射到新的[L,R],可以与原始索引一起使用。

为了更好地理解映射,让我们回到示例的第二步:

布尔数组为: 1 0 0 0 1 1 ,使用order-statistics属性删除L = 1和R = 2之间的元素,L将被映射为1,R将被映射为映射为5,现在我们将newL和newR之间的范围更新为零,并且布尔数组变为 0 0 0 0 0 1

代码

#include <bits/stdc++.h>

using namespace std;

class SegmentTree {
    vector<int> seg, lazy;
    int sz;

    void build(int ind, int ns, int ne, const vector<int> &v) {
        if (ns == ne) {
            seg[ind] = v[ns];
            return;
        }
        int mid = ns + (ne - ns) / 2;
        build(ind * 2, ns, mid, v);
        build(ind * 2 + 1, mid + 1, ne, v);
        seg[ind] = seg[ind * 2] + seg[ind * 2 + 1];
    }

    void probagateLazy(int ind) {
        if (lazy[ind]) {
            lazy[ind] = 0, seg[ind] = 0;
            if (ind * 2 + 1 < 4 * sz)
                lazy[ind * 2] = lazy[ind * 2 + 1] = 1;
        }
    }

    int query(int s, int e, int ind, int ns, int ne) {
        probagateLazy(ind);
        if (e < ns || s > ne)
            return 0;
        if (s <= ns && ne <= e)
            return seg[ind];
        int mid = ns + (ne - ns) / 2;
        return query(s, e, ind * 2, ns, mid) + query(s, e, ind * 2 + 1, mid + 1, ne);
    }

    void update(int s, int e, int ind, int ns, int ne) {
        probagateLazy(ind);
        if (e < ns || s > ne)
            return;
        if (s <= ns && ne <= e) {
            lazy[ind] = 1;
            probagateLazy(ind);
            return;
        }

        int mid = ns + (ne - ns) / 2;
        update(s, e, ind * 2, ns, mid);
        update(s, e, ind * 2 + 1, mid + 1, ne);
        seg[ind] = seg[ind * 2] + seg[ind * 2 + 1];
    }

    int find(int pos, int ind, int ns, int ne) {
        probagateLazy(ind);
        if (ns == ne)
            return ns;

        probagateLazy(ind * 2);
        probagateLazy(ind * 2 + 1);


        int mid = ns + (ne - ns) / 2;
        if (pos <= seg[ind * 2])
            return find(pos, ind * 2, ns, mid);
        return find(pos - seg[ind * 2], ind * 2 + 1, mid + 1, ne);
    }

public:
    SegmentTree(int sz, const vector<int> &v) {
        this->sz = sz;
        seg = vector<int>(sz * 4);
        lazy = vector<int>(sz * 4);
        build(1, 0, sz - 1, v);
    }

    int query(int s, int e) {
        return query(s, e, 1, 0, sz - 1);
    }

    int update(int s, int e) {
        update(s, e, 1, 0, sz - 1);
    }

    int find(int pos) {
        return find(pos, 1, 0, sz - 1);
    }
};


int main() {

    int i, n, k, l, r;
    scanf("%d %d", &n, &k);

    vector<int> a;
    for (i = 1; i <= n; i++) {
        a.push_back(i);
    }
    vector<int> v(n, 1);
    SegmentTree st(n, v);

    while (k--) {
        scanf("%d %d", &l, &r);
        int newL = st.find(l);
        int newR = st.find(r);
        st.update(newL, newR);
    }
    vector<int> ans;
    for (int i = 0; i < n; i++) {
        if (st.query(i, i))
            ans.push_back(a[i]);
    }
    printf("%d\n", ans.size());
    for (int i = 0; i < ans.size(); i++) {
        printf("%d ", ans[i]);
    }
}

将框树黑框

如果您不熟悉段树,那么应该会发现难以理解的代码,因此,我将通过忽略段树的内部实现来使它变得更容易,并让您快速了解一下它的功能。

查询方法

查询方法将要查询的范围的开始和结束索引作为输入,并返回该范围内元素的总和。

更新方法

update方法将要更新的范围的开始和结束索引作为输入,并将该范围内的所有项目设置为零

查找方法

find方法以输入 X 作为输入,并返回第一个元素 Y ,它们是 [0,Y] X

其他解决方案

使用Splay TreeTreap数据结构也可以解决问题。