1. 程式人生 > 實用技巧 >CodeForces Round #678(Div2) E.Complicated Computations Mex性質,權值線段樹

CodeForces Round #678(Div2) E.Complicated Computations Mex性質,權值線段樹

CodeForces Round #678(Div2) E.Complicated Computations Mex性質,權值線段樹

題意

定義\(mex\)表示給定序列中從1開始第一個沒有出現的數

給定一組正整數,問其\(substring\)\(mex\)\(mex\)是多少

\[1\leq n \leq 10^5 \\ 1\leq a_i \leq n \]

分析

此題和\(HDU-6767\)有點相似,都用到了線段樹和mex的性質,只不過那題似乎比這題麻煩一點

我們考慮先求出所有\(substring\)\(mex\),如果一個區間的\(mex = a\),那麼容易發現只需滿足如下條件

  • 區間中沒有出現\(a\)
  • 區間中出現了\(1 到 a - 1\)

想象在考慮\(a\)的時候把整個陣列分割成了若干個不含\(a\)的子段,那麼對於其中一個\(a\),它只會影響到上一個或者下一個\(a\)之後的區間,

這樣就只需要滿足第二個條件,而這個可以用權值線段樹維護

線段樹維護\(a_i\)最後一次的位置,動態維護一顆權值線段樹即可

操作只需要單點修改,區間查詢

注意用\(last\)維護的是上一個位置,因此最後還需掃一遍,來確定後面的貢獻

程式碼

int last[maxn], a[maxn];
int val[maxn << 2];

int vis[maxn];

void update(int i, int l, int r, int pos, int v) {
    if (l == r) {
        val[i] = v;
        return;
    }
    int mid = l + r >> 1;
    if (pos <= mid) update(i << 1, l, mid, pos, v);
    else update(i << 1 | 1, mid + 1, r, pos, v);
    val[i] = min(val[i << 1], val[i << 1 | 1]);
}

int query(int i, int l, int r, int L, int R) {
    if (l == L && r == R) {
        return val[i];
    }
    int mid = l + r >> 1;
    if (R <= mid) return query(i << 1, l, mid, L, R);
    else if (L > mid) return query(i << 1 | 1, mid + 1, r, L, R);
    else return min(query(i << 1, l, mid, L, mid), query(i << 1 | 1, mid + 1, r, mid + 1, R));
}

int main() {
    int n = readint();
    for (int i = 1; i <= n; i++)
        a[i] = readint();
    for (int i = 1; i <= n; i++) {
        if (a[i] != 1) vis[1] = 1;
        if (a[i] > 1 && query(1, 1, n, 1,a[i] - 1) > last[a[i]]) vis[a[i]] = 1;
        last[a[i]] = i;
        update(1, 1, n, a[i], i);
    }
    for (int i = 2; i <= n + 1; i++)
        if (query(1, 1, n, 1, i - 1) > last[i]) vis[i] = 1;
    int res = 1;
    while (vis[res]) res++;
    cout << res;
}