CF1083C Max Mex 線段樹

題面

CF1083C Max Mex

題解

首先我們考慮,如果一個數x是某條路徑上的mex,那麼這個數要滿足什麼條件?

  • 1 ~ x - 1的數都必須出現過.
  • x必須沒出現過。

現在我們要最大化x,那麼也就意味着我們要找到一條路徑使得這個都出現過的前綴儘可能長。
第二個條件可以忽略,因爲如果第1個條件滿足,而第2個條件卻不滿足,意味着我們可以把x至少擴大1位,因爲要求最大值,所以擴大肯定最優,因此我們肯定會擴大到不能擴大爲止。
由此我們可以發現,x是滿足可二分性的。

考慮在線段樹上維護這個問題,區間\([l, r]\)表示值域區間爲\([l, r]\)的點全都連接在一起形成的一條路徑。
如果有這條路徑,那麼就存下左右端點,否則就不存。
那麼考慮如何合併2個區間\([l, mid], [mid + 1, r]\)
從4個端點中任取2個作爲新的端點,如果另外2個端點均在這2個端點構成的路徑上,那麼我們就找到了一條新路徑滿足值域屬於\([l, r]\)的點全在路徑上。
這樣做的正確性(最優性)依賴於權值兩兩不同,因此我們的選擇是唯一的,因此肯定是最優選擇。

有一個很簡潔的式子可以用於判斷一個點是否在某條路徑上:
如果\(x\)在鏈\((u, v)\)上,設\(LCA(u, v) = y\)那麼有:
\[((LCA(u, x) == x \oplus LCA(v, x) == x) \oplus LCA(x, y) == y)\]

於是維護好之後剩下的事情就是在線段樹上查詢(二分)了。

#include<bits/stdc++.h>
using namespace std;
#define R register int
#define LL long long
#define AC 201000
#define ac 802000
#define h(x) ((x) << 1)

int n, m;
int Head[AC], date[AC], Next[AC], tot;
int v[AC], fa[AC], s[AC];//s[i]表示權值爲i的是哪個,只需要在最開始用就可以了

inline int read()
{
    int x = 0;char c = getchar();
    while(c > '9' || c < '0') c = getchar();
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x;
}

inline void add(int f, int w)
{date[++ tot] = w, Next[tot] = Head[f], Head[f] = tot;}

#define cal(x, y) ((dep[x] < dep[y]) ? x : y)//返回x, y中dep最小的那個
#define maxn 401000
int st[maxn][20], ss[maxn], dep[AC], t[maxn], p[maxn], first[AC], top;
struct ST_form{

    void dfs(int x)//求遍歷的數組 + dep
    {
        ss[++ top] = x, first[x] = top;
        for(R i = Head[x]; i; i = Next[i])
        {
            int now = date[i];
            dep[now] = dep[x] + 1, dfs(now), ss[++ top] = x;//返回的時候也要加
        }
    }

    void build()
    {
        dep[1] = 1, dfs(1);
        int tmp = 1, cnt = 0;
        for(R i = 1; i <= top; i ++) 
        {
            if(i == (tmp << 1)) tmp <<= 1, ++ cnt;
            p[i] = tmp, t[i] = cnt;
        }
        for(R i = 1; i <= top; i ++) st[i][0] = ss[i];
        tmp = 1;
        for(R i = 1; i <= 18; i ++)
        {
            for(R j = 1; j <= top; j ++)
                st[j][i] = cal(st[j][i - 1], st[min(j + tmp, top)][i - 1]);
            tmp <<= 1;
        }
    }

    inline int LCA(int x, int y)//st表求LCA
    {
        int l = first[x], r = first[y];
        if(l > r) swap(l, r);//不一定按順序的
        int len = r - l + 1;
        return cal(st[l][t[len]], st[r - p[len] + 1][t[len]]);
    }
}T;

struct node{
    int lx, rx;
}tree[ac], go;

#define update(x) tree[x] = merge(tree[x << 1], tree[(x << 1) + 1]);
struct seg_tree{

    bool check(int x, int u, int v)//檢查x是否在路徑(u, v)上
    {
        int y = T.LCA(u, v);
        return ((T.LCA(u, x) == x || T.LCA(v, x) == x) && T.LCA(x, y) == y);
    }
    
    inline node merge(node ll, node rr)
    {
        if(!ll.lx || !rr.lx) return (node){0, 0}; 
        int s[5] = {ll.lx, ll.rx, rr.lx, rr.rx};
        for(R i = 0; i < 4; i ++)
            for(R j = i + 1; j < 4; j ++)
            {
                bool flag = true;
                if(s[i] == s[j]) continue;
                for(R k = 0; k < 4; k ++)
                    if(k != i && k != j && !check(s[k], s[i], s[j])) {flag = false; break;}
                if(flag) return (node){s[i], s[j]};
            } 
        return (node){0, 0};
    }

    void build(int x, int l, int r)
    {
        if(l == r) {tree[x].lx = tree[x].rx = s[l]; return ;}   
        int mid = (l + r) >> 1;
        build(h(x), l, mid), build(h(x) + 1, mid + 1, r);
        update(x);
    }

    void change(int x, int l, int r, int w)
    {
        if(l == r) {tree[x].lx = tree[x].rx = w; return ;}
        int mid = (l + r) >> 1;
        if(v[w] <= mid) change(h(x), l, mid, w);
        else change(h(x) + 1, mid + 1, r, w);   
        update(x);
    }
    
    int find(int x, int l, int r)
    {
        int mid = (l + r) >> 1;
        if(l == r) 
        {
            node tmp = go.lx ? merge(go, tree[x]) : tree[x];
            if(tmp.lx) {go = tmp; return 1;}
            return 0;
        }
        if(!tree[h(x)].lx) return find(h(x), l, mid);
        else 
        {
            node tmp = go.lx ? merge(go, tree[h(x)]) : tree[h(x)]; 
            if(!tmp.lx) return find(h(x), l, mid);
            else {go = tmp; return mid - l + 1 + find(h(x) + 1, mid + 1, r);}
        }
    }

}T1;

void pre()
{
    n = read();
    for(R i = 1; i <= n; i ++) v[i] = read() + 1, s[v[i]] = i;
    for(R i = 2; i <= n; i ++) fa[i] = read(), add(fa[i], i);
}

void work()
{
    m = read();
    for(R i = 1; i <= m; i ++)
    {
        int opt = read();
        if(opt == 1)
        {
            int x = read(), y = read();
            swap(v[x], v[y]);
            T1.change(1, 1, n, x), T1.change(1, 1, n, y);
        }
        else go.lx = go.rx = 0, printf("%d\n", T1.find(1, 1, n));//因爲向右平移了1,所以要移回來,+ 1 - 1就恰好抵消了
    }
}

int main()
{
//  freopen("in.in", "r", stdin);
    pre();
    T.build();
    T1.build(1, 1, n);
    work();
//  fclose(stdin);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章