请告诉我Range Mex Query的有效算法

时间:2017-01-13 11:08:25

标签: c++ algorithm game-theory

我对这个问题有疑问。

问题

  • 您将获得一个序列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)很慢。请告诉我更快的算法。

2 个答案:

答案 0 :(得分:4)

这是一个i解决方案:

  1. 让我们从左到右遍历数组中的所有位置,并将每个值的最后一次出现存储在一个分段树中(分段树应该在每个节点中存储最小值)。

  2. 添加i号后,我们可以回答所有查询,其边框等于x

  3. 答案是最小值last[x] < ll。我们可以通过从根开始向下看段树(如果左子项中的最小值小于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) ,我们去那里。否则,我们去右边的孩子)。

  4. 就是这样。

    这是一些伪代码:

    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的速度是否足够快,但可能会进一步优化。