【问题标题】:How to erase elements more efficiently from a vector or set?如何更有效地从向量或集合中擦除元素?
【发布时间】:2017-04-08 16:42:52
【问题描述】:

问题陈述:

输入:

前两个输入是整数 n 和 m。 n 是在比赛中战斗的骑士数量(2

下一行包含 n 个功率等级。

接下来的 m 行包含两个整数 l 和 r,表示在第 i 场战斗中竞争的骑士位置范围。

每场战斗结束后,除功率最高者外的所有夜晚都将被淘汰。

每场战斗的范围是根据骑士的新位置给出的,而不是原来的位置。

输出:

输出 m 行,第 i 行包含那场战斗中骑士的原始位置(索引)。每行按升序排列。

示例输入:

8 4
1 0 5 6 2 3 7 4
1 3
2 4
1 3
0 1

样本输出:

1 2
4 5
3 7
0

这是这个过程的可视化。

          1     2
[(1,0),(0,1),(5,2),(6,3),(2,4),(3,5),(7,6),(4,7)]
       -----------------
                4     5
[(1,0),(6,3),(2,4),(3,5),(7,6),(4,7)]
             -----------------
          3           7
[(1,0),(6,3),(7,6),(4,7)]
       -----------------
    0
[(1,0),(7,6)]
 -----------

[(7,6)]

我已经解决了这个问题。我的程序产生了正确的输出,但是,它是 O(n*m) = O(n^2)。我相信如果我更有效地从矢量中删除骑士,效率可以提高。使用集合擦除元素会更有效吗? IE。擦除连续片段而不是单个骑士。有没有更有效的替代方法?

#define INPUT1(x)  scanf("%d", &x)
#define INPUT2(x, y)  scanf("%d%d", &x, &y)
#define OUTPUT1(x) printf("%d\n", x);

int main(int argc, char const *argv[]) {
    int n, m;
    INPUT2(n, m);
    vector< pair<int,int> > knights(n);
    for (int i = 0; i < n; i++) {
        int power;
        INPUT(power);
        knights[i] = make_pair(power, i);
    }
    while(m--) {
        int l, r;
        INPUT2(l, r);
        int max_in_range = knights[l].first;
        for (int i = l+1; i <= r; i++) if (knights[i].first > max_in_range) {
            max_in_range = knights[i].first;
        }
        int offset = l;
        int range = r-l+1;
        while (range--) {
            if (knights[offset].first != max_in_range) {
                OUTPUT1(knights[offset].second));
                knights.erase(knights.begin()+offset);
            }
            else offset++;
        }
        printf("\n");
    }
}

【问题讨论】:

    标签: c++ algorithm vector set


    【解决方案1】:

    嗯,从向量中删除肯定不会有效。从集合或无序集合中移除会更有效(使用迭代器而不是索引)。

    但问题仍然存在 O(n^2),因为在运行 n*m 次时有两个嵌套。

    --编辑--

    我相信我现在明白了这个问题 :) 首先让我们计算一下上面代码的复杂度。最糟糕的情况是所有战斗的最大射程为 1(每场战斗两个晚上),并且战斗没有根据位置排序。这意味着你有 m 场战斗(在这种情况下 m = n-1 ~= O(n)

    • 第一个 while 循环运行 n
    • 每次运行一次,总共 n*1 = n
    • 第二个 while 循环每次运行一次,使其再次 n
      • 从向量中删除意味着 n-1 移位,使其变为 O(n)

    因此与向量的复杂度总复杂度为O(n^2)

    首先,您实际上并不需要内部 for 循环。以第一个骑士为范围内的最大值,将范围内的其余骑士一一比较,并移除击败的骑士。

    现在,我相信使用 std::map 可以在 O(nlogn) 中完成。地图的关键是位置,值是骑士的等级。

    在继续之前,在 map 中查找和删除元素是对数的,迭代是恒定的。

    最后,您的代码应如下所示:

    while(m--) // n times
        strongest = map.find(first_position); // find is log(n) --> n*log(n)
    
        for (opponent = next of strongest; // this will run 1 times, since every range is 1
             opponent in range;
             opponent = next opponent) // iterating is constant
           // removing from map is log(n) --> n * 1 * log(n)
           if strongest < opponent
               remove strongest, opponent is the new strongest
           else
               remove opponent, (be careful to remove it after iterating to next)
    

    好的,现在上限将是 O(2*nlogn) = O(nlogn)。如果范围增加,则上层循环的运行时间会减少,但会增加删除操作的次数。我确定上限不会改变,让我们把它作为一个家庭作业让你计算:)

    【讨论】:

    • 能否请您详细说明我如何使用集合做到这一点?
    • 是否可以在 O(nlogn) 中解决这个问题?是否有可以有效地将新索引映射到旧索引的数据结构或算法?
    • 另外,您能否详细说明为什么从集合中擦除比从向量中擦除更有效?
    • 对于第一个问题,我没有真正理解真正的问题。所以我不知道你是否可以做到nlogn。其次,这是因为它们存储的数据结构。一个向量可以被认为是一个增强的数组。这意味着如果您从中间移除一个元素,您需要进行轮班。 “集合通常实现为二叉搜索树”,在一般情况下,擦除应该更快。
    • 简单来说,问题是,在每场战斗之后,你都想打印出阵亡骑士的原始位置并清除掉阵亡骑士。战斗范围是根据战士的新位置给出的,而不是原来的位置。
    【解决方案2】:

    带有陷阱的解决方案非常简单。

    对于每一个查询,都需要通过隐式key拆分treap,得到[l, r]范围对应的子树(需要O(log n)时间)。 之后,您可以遍历子树并找到具有最大力量的骑士。之后,您只需要将treap 的[0, l)[r + 1, end) 部分与该骑士对应的节点合并即可。

    很明显,除了子树遍历和打印之外,解决方案的所有部分都在每个查询的O(log n) 时间内工作。然而,每个操作只重新插入一个骑士并从范围中删除其余的骑士,因此输出的大小(以及子树大小的总和)在n 中是线性的。所以总的时间复杂度是O(n log n)

    我认为你不能用标准的 stl 容器来解决,因为没有标准的容器支持通过索引快速获取迭代器并删除任意元素。

    【讨论】:

    • 我不明白如何从treap 中获取与[l, r] 范围相对应的子树。另外,treap 的每个节点都会是一对吗? IE。 (功率,初始位置)?
    猜你喜欢
    • 2021-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多