简要介绍问题的情况:
给出从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 << ' ';
}
}
如何解决这个问题?
答案 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 Tree或Treap数据结构也可以解决问题。