【问题标题】:Go through the array from left to right and collect as many numbers as possible从左到右遍历数组并收集尽可能多的数字
【发布时间】:2021-05-14 17:29:27
【问题描述】:

CSES 问题 (https://cses.fi/problemset/task/2216/)。

给你一个数组,其中包含 1…n 之间的每个数字恰好一次。你的任务是按递增顺序收集从 1 到 n 的数字。 在每一轮中,您从左到右遍历数组并收集尽可能多的数字。总轮数是多少? 约束:1≤n≤2⋅10^5

这是我在 c++ 上的代码:

int n, res=0;
cin>>n;
int arr[n];
set <int, greater <int>> lastEl;
for(int i=0; i<n; i++) {
    cin>>arr[i];
    auto it=lastEl.lower_bound(arr[i]);
    if(it==lastEl.end()) res++;
    else lastEl.erase(*it);
    lastEl.insert(arr[i]);
}
cout<<res;

我遍历数组一次。如果元素 arr[i] 小于之前的所有元素,则我“打开”一个新序列,并将该元素保存为该序列中的最后一个元素。我将已打开序列的最后一个元素存储在集合中。如果 arr[i] 小于之前的一些元素,那么我采用已经存在的具有最大最后一个元素(但小于 arr[i])的序列,并用 arr[i] 替换该序列的最后一个元素。 唉,它只适用于三个给定的两个测试,而第三个测试的输出比它应该的要少得多。我做错了什么?

【问题讨论】:

  • 为什么你的代码中有set?你能用简单的英语解释你的代码试图做什么吗?或者,它应该如何工作?此外,链接的问题仅包含一个示例,因此您关于输出的陈述没有意义。
  • 我遍历数组一次。如果元素 arr[i] 小于之前的所有元素,则我“打开”一个新序列,并将该元素保存为该序列中的最后一个元素。我将已打开序列的最后一个元素存储在 set 中。如果元素 arr[i] 小于之前的一些元素,那么我采用其中最大的现有序列(但小于 arr[i]),并用 arr[ 替换该序列的最后一个元素一世]。我的意思是三个测试,在下载的解决方案上执行。感谢您的反馈。

标签: c++ algorithm sorting sequence


【解决方案1】:

让我详细解释一下我的思考过程,以便您下次遇到相同类型的问题时更容易。

首先,我在面对这类问题时经常犯的一个错误就是想模拟这个过程。问题陈述中提到的“模拟过程”是什么意思?问题提到了一个回合的发生,以最大化按特定顺序增加数字的集合。所以,你从1开始,找到它,看到下一个数字2不在它之外,即2不能和1在同一轮,形成一个递增序列。所以,我们需要对2 再进行一轮。现在我们发现,23 都可以在同一轮中收集,因为我们从左到右移动并按递增顺序获取数字。但我们不能接受4,因为它在2 之前开始。最后,对于45,我们需要再进行一轮。总共进行了三轮。

现在,如果您以这种方式模拟该过程,则问题变得非常容易解决。在第一轮中,您查找以1 开头的递增序列的数字。您在开始第二轮之前删除这些数字。你继续这种方式,直到你用尽了所有的数字。

但是模拟这个过程会导致时间复杂度不会超过问题陈述中提到的约束。因此,我们需要找出另一种方式,在不模拟整个过程的情况下提供相同的输出。

请注意,数字的位置在这里很重要。为什么2 需要再进行一轮?因为它出现在1 之前。 3 不需要再进行一轮,因为它在 2 之后。同样,我们需要对4 再进行一轮,因为它在2 之前。

因此,在考虑每个数字时,我们只需要关注其前面的数字在顺序中的位置即可。在考虑2时,我们看1的位置? 1 是在 2 之前还是之后?它来了,我们不需要另一轮。但如果它之前出现,我们将需要一个额外的回合。对于每个数字,我们都会查看此条件并在必要时增加轮数。这样,我们可以在不模拟整个过程的情况下计算出总轮数。

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char const *argv[])
{
    int n;
    cin >> n;
    vector <int> v(n + 1), pos(n + 1);
    for(int i = 1; i <= n; ++i){
        cin >> v[i];
        pos[v[i]] = i;
    }
    int total_rounds = 1; // we'll always need at least one round because the input sequence will never be empty
    for(int i = 2; i <= n; ++i){
        if(pos[i] < pos[i - 1]) total_rounds++;
    }
    cout << total_rounds << '\n';
    return 0;
}

下次当您遇到此类问题时,请暂停片刻,并尝试控制您在代码中模拟过程的冲动。几乎可以肯定,会有一些聪明的观察可以让您获得最佳解决方案。

【讨论】:

  • 其实我理解错了任务。我没有意识到每个子序列都必须由相邻的数字组成。
  • 根据你的说法,如果一个数字在某个回合的某个数字之前,那么你需要开始一个新的回合。但是,如果它是在另一轮之后发生的呢?在这种情况下,我们不需要创建新一轮。我在这里详细问过这个问题:discuss.codechef.com/t/cses-collecting-numbers/83775/…
猜你喜欢
  • 1970-01-01
  • 2014-04-12
  • 1970-01-01
  • 2017-12-20
  • 1970-01-01
  • 2020-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多