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

How to remove all element between given indexes?

本文关键字:之间 索引 元素 何删除 删除      更新时间:2023-10-16

简要介绍问题的条件:

给定从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的数字向量,并删除了范围[左,右]中的所有元素,但由于复杂性,时间限制即将到来。

#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 << ' ';
}
}

如何解决这个问题?

使用具有延迟传播和O((N+Q)*log(N))中的顺序统计的分段树可以解决该问题,考虑到您的限制,该分段树应该在一两秒钟内通过大多数在线评委。

简要说明

"仍然存在">布尔数组

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

分段树

信息查询:让我们在这个布尔数组的顶部构建一个范围和段树(True映射到1,false映射到0)。如果我们查询任何[L,R]范围,则分段树的答案是仍然存在的元素的数量(注意,L和R是原始数组中的索引,包括已移除和未移除的元素)

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

最终输出

在更新所有为零的范围后,我们可以迭代每个索引,检查它是零还是一,如果是一,则打印它。然而,解决方案并不容易,因为输入中提供的范围不是原始数组中的范围,而是更新数组中的索引。

更新的范围问题

为了更好地理解这个问题,让我们来看看一个例子:

假设我们使用的是一个长度为6的数组,数组最初为:1 2 3 4 5 6,布尔数组最初为

让我们假设第一个删除是[2,4],现在新的数组是:1 5 6,新更新的布尔数组是:1 0 0 1 1

在这一点上,如果我们被要求打印数组,我们将简单地遍历原始数组,并打印布尔数组中只对应于true的值。

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

订单统计以解决更新范围问题

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

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

布尔数组是:1 0 0 1 1,删除L=1和R=2之间的元素,使用order statistics属性,L将映射到1,R将映射到5,现在我们将更新newL和newR之间的范围为零,布尔数组变为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("%dn", ans.size());
for (int i = 0; i < ans.size(); i++) {
printf("%d ", ans[i]);
}
}

黑拳段树

如果你不熟悉分段树,那么它可能会发现代码很难理解,所以我会忽略分段树的内部实现,让它变得更容易,并让你快速了解它的功能。

查询方法

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

更新方法

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

查找方法

find方法将X作为输入,并返回第一个元素Y[0,Y]范围内元素的总和X

其他解决方案

该问题也可以使用Splay Tree或Treap数据结构来解决。