Codeforces Round #767 (Div. 2)


呜,一学期都没打cf,久而久之变得生疏和害怕了,寒假第一场cf就如此...惨烈,不过也是随着补题才有提升嘛。

C. Meximum Array

mex...之前只在博弈论之类的见到过...没自己实现过呜。

这题很重要的一点是对于固定起点的一系列前缀求mex数组的话,这个数组是单调不减的,并且上升速度不会超过1/1

也就是说,我们设当前位置作为末尾的数组的mex值为 j 的话,每次移动位置后,只要将原数组当前位置的值在mark数组中做好标记,然后让 j  一直++直到遇到未标记的位置,这个 j 就是当前位置作为末尾的数组的mex值。

以上是线性时间复杂度求前缀mex的办法,此题还有个关键是要记录mex数组第一次达到最大值的位置,我一开始想的是,每次增长就记录一下位置,最后自然得到结果。但是这个方法实际上复杂度过高了,因为很有可能2*105的数组只要4位mex就达到了最大(test 2),O(n2)中每次后面的计算都是多余的,因为每个都计算了n次。

这时候灵机一动发现,只要我提前用O(n)算出后缀的mex,那么第一次达到最大也就只要比较 j 等于后缀的mex就行了。注意这里的后缀的开头是指上一次循环中记录的第一次最大位置,也是这次的开始位置(即使在补题时还是大意吃了发WA(悲))。

AC代码:

#include 
#define ll long long
#define F freopen("in.txt", "r", stdin)
using namespace std;
int num[200005], mrk[200005], mex2[200005];
int main()
{
    //F;
    int t;
    cin >> t;
    while (t--)
    {
        vector<int> ans;
        int n;
        cin >> n;
        int x = 1;
        for (int i = 1; i <= n; i++)
            cin >> num[i];
        int j2 = 0;
        for (int i = n; i > 0; i--)
        {
            mrk[num[i]] = true;
            while (mrk[j2])
                j2++;
            mex2[i] = j2;
        }
        memset(mrk, 0, sizeof(mrk));
        while (x <= n)
        {
            //cout << "x:" << x << " ";
            int j = 0, maxj = 0;
            for (int i = x; i <= n; i++)
            {
                mrk[num[i]] = true;
                while (mrk[j])
                    j++;
                if (j == mex2[x] || i == n)
                {
                    maxj = j;
                    x = i + 1;
                    break;
                }
            }
            ans.push_back(maxj);
            memset(mrk, 0, sizeof(mrk));
        }
        int st = ans.size();
        cout << st << endl;
        for (int i = 0; i < st; i++)
            cout << ans[i] << (i == st - 1 ? "" : " ");
        cout << endl;
        memset(mex2, 0, sizeof(mex2));
    }
    return 0;
}