我对这个问题有疑问。
问题
a[0], a 1],..., a[N-1]
和一组范围(l[i], r[i]) (0 <= i <= Q - 1)
。mex(a[l[i]], a[l[i] + 1],..., a[r[i] - 1])
计算(l[i], r[i])
。函数mex是最小排除值。
Wikipedia Page of mex function
您可以假设N <= 100000, Q <= 100000, and a[i] <= 100000
O(N * (r[i] - l[i]) log(r[i] - l[i]) )
算法很明显,但效率不高。
我当前的方法
#include <bits/stdc++.h>
using namespace std;
int N, Q, a[100009], l, r;
int main() {
cin >> N >> Q;
for(int i = 0; i < N; i++) cin >> a[i];
for(int i = 0; i < Q; i++) {
cin >> l >> r;
set<int> s;
for(int j = l; j < r; j++) s.insert(a[i]);
int ret = 0;
while(s.count(ret)) ret++;
cout << ret << endl;
}
return 0;
}
请告诉我如何解决。
编辑:O(N ^ 2)很慢。请告诉我更快的算法。
答案 0 :(得分:4)
这是一个i
解决方案:
让我们从左到右遍历数组中的所有位置,并将每个值的最后一次出现存储在一个分段树中(分段树应该在每个节点中存储最小值)。
添加i
号后,我们可以回答所有查询,其边框等于x
。
答案是最小值last[x] < l
,l
。我们可以通过从根开始向下看段树(如果左子项中的最小值小于tree = new SegmentTree() // A minimum segment tree with -1 in each position
for i = 0 .. n - 1
tree.put(a[i], i)
for all queries with r = i
ans for this query = tree.findFirstSmaller(l)
,我们去那里。否则,我们去右边的孩子)。
就是这样。
这是一些伪代码:
int findFirstSmaller(node, value)
if node.isLeaf()
return node.position()
if node.leftChild.minimum < value
return findFirstSmaller(node.leftChild, value)
return findFirstSmaller(node.rightChild)
查找较小的函数如下:
findFisrtSmaller
这个解决方案很容易编码(你需要的只是点更新和上面显示的$ protractor conf.js
(node:10648) DeprecationWarning: os.tmpDir() is deprecated. Use os.tmpdir() instead.
[11:25:05] I/hosted - Using the selenium server at http://127.0.0.1:4444/wd/hub
[11:25:05] I/launcher - Running 1 instances of WebDriver
[11:25:09] E/launcher - Error: TypeError: Cannot call a class as a function
at exports.default (C:\Source\test\node_modules\babel-runtime\helpers\classCallCheck.js:7:11)
at Object.Cli (C:\Source\test\node_modules\cucumber\lib\cli\index.js:64:34)
at C:\Source\test\node_modules\protractor-cucumber-framework\index.js:31:16
at Function.promise (C:\Source\test\node_modules\q\q.js:682:9)
at C:\Source\test\node_modules\protractor-cucumber-framework\index.js:24:14
at _fulfilled (\\hermes\vhd_profiles\VDI_Home_VHD1\modisej\AppData\Roaming\npm\node_modules\protractor\node_modules\q\q.js:834:54)
at self.promiseDispatch.done (\\hermes\vhd_profiles\VDI_Home_VHD1\modisej\AppData\Roaming\npm\node_modules\protractor\node_modules\q\q.js:863:30)
at Promise.promise.promiseDispatch (\\hermes\vhd_profiles\VDI_Home_VHD1\modisej\AppData\Roaming\npm\node_modules\protractor\node_modules\q\q.js:796:13)
at \\hermes\vhd_profiles\VDI_Home_VHD1\modisej\AppData\Roaming\npm\node_modules\protractor\node_modules\q\q.js:556:49
at runSingle (\\hermes\vhd_profiles\VDI_Home_VHD1\modisej\AppData\Roaming\npm\node_modules\protractor\node_modules\q\q.js:137:13)
at flush (\\hermes\vhd_profiles\VDI_Home_VHD1\modisej\AppData\Roaming\npm\node_modules\protractor\node_modules\q\q.js:125:13)
at _combinedTickCallback (internal/process/next_tick.js:67:7)
at process._tickCallback (internal/process/next_tick.js:98:9)
[11:25:09] E/launcher - Process exited with error code 100
函数,我确信它对于给定的约束来说足够快。
答案 1 :(得分:1)
让我们以从左到右的方式处理我们的查询和元素,例如
for (int i = 0; i < N; ++i) {
// 1. Add a[i] to all internal data structures
// 2. Calculate answers for all queries q such that r[q] == i
}
这里我们对此循环进行了O(N)
次迭代,我们希望同时更新数据结构并在o(N)
时间内查询当前处理部分后缀的答案。
如果contains[i][j]
位置的后缀包含数字1
,则i
使用j
数组0
。还要考虑我们分别为每个contains[i]
计算了前缀和。在这种情况下,我们可以使用二分搜索在O(log N)
时间内回答每个特定的后缀查询:我们应该在相应的contains[l[i]]
数组中找到第一个零,这恰好是部分和等于的第一个位置索引,而不是索引+ 1.不幸的是,这样的数组需要O(N^2)
空间,每次更新需要O(N^2)
次。
所以,我们必须优化。让我们用“求和”和“赋值”范围操作构建一个二维range tree。在这样的树中,我们可以查询任何子矩形的和,并在O(log^2 N)
时间内为任何子矩形的所有元素分配相同的值,这允许我们在O(log^2 N)
时间和查询中进行更新在O(log^3 N)
时间内,给出时间复杂度O(Nlog^2 N + Qlog^3 N)
。使用延迟初始化可以实现空间复杂度O((N + Q)log^2 N)
(以及数组初始化的同一时间)。
UP:让我们用“sum”修改查询在范围树中的工作方式。对于一维树(不要让这个答案太长),它是这样的:
class Tree
{
int l, r; // begin and end of the interval represented by this vertex
int sum; // already calculated sum
int overriden; // value of override or special constant
Tree *left, *right; // pointers to children
}
// returns sum of the part of this subtree that lies between from and to
int Tree::get(int from, int to)
{
if (from > r || to < l) // no intersection
{
return 0;
}
if (l <= from && to <= r) // whole subtree lies within the interval
{
return sum;
}
if (overriden != NO_OVERRIDE) // should push override to children
{
left->overriden = right->overriden = overriden;
left->sum = right->sum = (r - l) / 2 * overriden;
overriden = NO_OVERRIDE;
}
return left->get(from, to) + right->get(from, to); // split to 2 queries
}
鉴于在我们的特定情况下,对树的所有查询都是前缀和查询,from
总是等于0
,因此,对子项的一个调用总是返回一个简单的答案({{ 1}}或已计算0
)。因此,我们可以在二进制搜索算法中对二维树进行sum
查询,而不是对此O(log N)
查询非常类似地实现搜索的临时过程。它应该首先得到左子项的值(由于它已经计算得到get
),然后检查我们要查找的节点是否在左边(这个总和小于左边的叶子数量)子树)并根据此信息向左或向右移动。此方法将进一步优化查询到O(1)
时间(因为它现在是一个树操作),从而导致 O(log^2 N)
的时间和空间复杂性。
不确定此解决方案对O((N + Q)log^2 N))
和Q
最高N
的速度是否足够快,但可能会进一步优化。