CF1083C Max Mex 解題報(bào)告 (線段樹(shù) + LCA)


題目大意

鏈接

給定一棵 N 個(gè)節(jié)點(diǎn)的樹(shù),每個(gè)點(diǎn)有各自的點(diǎn)權(quán) pi 器罐,但是邊的權(quán)值都是 1 (也可以認(rèn)為沒(méi)有邊權(quán))。保證所有點(diǎn)的點(diǎn)權(quán)是所有小于 N 的非負(fù)整數(shù)的一個(gè)排列。

定義函數(shù) y = mex(S) 归薛,其中 S 表示一個(gè)非負(fù)整數(shù)集, y 表示不在這個(gè)集合里的最小非負(fù)整數(shù)匪蝙。

現(xiàn)有 Q 組詢問(wèn)主籍。詢問(wèn)有兩個(gè)類型:

  1. 交換兩個(gè)給定點(diǎn)的點(diǎn)權(quán)
  2. 如果將樹(shù)上的任意一條簡(jiǎn)單路徑 L 經(jīng)過(guò)的點(diǎn)的點(diǎn)權(quán)看作一個(gè)非負(fù)整數(shù)集,則可求得其對(duì)應(yīng)的 mex(L) 」淝颍現(xiàn)要求輸出當(dāng)前樹(shù)上所有路徑的最大 mex 值千元,即 max(mex(L))

題目保證 NQ 不超過(guò) 2 × 105

分析

這個(gè)題剛看到的時(shí)候感覺(jué)有兩個(gè)可能的切入點(diǎn)。題目中間定義的這個(gè) mex 函數(shù)可以算是對(duì)于前綴的查詢颤绕,然后交換點(diǎn)權(quán)可以看成兩次連續(xù)的點(diǎn)修改幸海。這也就是說(shuō)這題可能可以用樹(shù)狀數(shù)組或者線段樹(shù)解決。第二個(gè)點(diǎn)就是題目中多次提到的路徑奥务,讓我有一種樹(shù)剖的感覺(jué)物独。但是鑒于我不會(huì)樹(shù)剖所以這條路就沒(méi)往下想...

嘗試在線段樹(shù)的框架下考慮這個(gè)問(wèn)題,如果線段 [L, R] 對(duì)應(yīng)的線段樹(shù)節(jié)點(diǎn)表示對(duì)于閉區(qū)間 [L, R] 中的所有數(shù)都出現(xiàn)的最短簡(jiǎn)單路徑(當(dāng)然也可能無(wú)解)氯葬,假如我們可以快速維護(hù)這棵線段樹(shù)挡篓,那么每次二型詢問(wèn)就可以在 O(T × log2n) 內(nèi)通過(guò)二分地詢問(wèn)線段樹(shù)完成(其中 T 表示單次維護(hù)時(shí)間);每次一型詢問(wèn)可以分拆成兩個(gè)點(diǎn)修改帚称,修改葉子節(jié)點(diǎn)以后維護(hù)它的所有祖先官研,復(fù)雜度 O(T × logn) ∈郎保看起來(lái)算是一個(gè)不那么爆炸的復(fù)雜度了阀参。

下面的問(wèn)題就是如何維護(hù)(合并)兩個(gè)鄰接區(qū)間了。首先瞻坝,線段樹(shù)的所有葉子區(qū)間都有解(寬度為 1)蛛壳。其次,父節(jié)點(diǎn)有解的必要(但是不充分)條件是它的兩個(gè)兒子都有解所刀。然后我仔細(xì)想了一下衙荐,樹(shù)上兩條路徑可以合并的充要條件應(yīng)該是這些:

  • 兩條路徑滿足包含關(guān)系,即某條路徑的兩個(gè)端點(diǎn)都在另一條路徑上
  • 兩條路徑各有一個(gè)端點(diǎn)在另一條路徑上
  • 可以從兩條路徑中各選一個(gè)端點(diǎn)使得剩下的兩個(gè)端點(diǎn)在被選的兩個(gè)端點(diǎn)確定的路徑上

大概感受了一下這些條件跟存在四個(gè)端點(diǎn)中的兩個(gè)使得剩下兩個(gè)在它們確定的路徑上是等價(jià)的浮创。于是就轉(zhuǎn)化成了判斷點(diǎn)是否在路徑上的問(wèn)題忧吟。

這個(gè)問(wèn)題可以用 LCA 來(lái)解決, 假設(shè)一條路徑的端點(diǎn)為 E1E2 斩披,點(diǎn) M 在這條路徑上的充要條件是:

A = LCA(E1, E2)
(LCA(E1, M) == M || LCA(E2, M) == M) && LCA(A, M) == A

LCA 的話如果用離散表預(yù)處理的話可以做到 O(1) 查詢溜族。這樣就差不多想清楚了讹俊。
然后交了,TLE...

仔細(xì)算了一下復(fù)雜度煌抒,這個(gè) LCA 雖然是 O(1) 的查詢仍劈,但是在極端情況下一次維護(hù)竟然能查詢 48LCA 。加上 LCA 本身常數(shù)也不小寡壮,復(fù)雜度會(huì)非常爆炸...

所幸我很快就發(fā)現(xiàn)贩疙,二分在最壞情況下會(huì)有非常多的重復(fù)詢問(wèn)(因?yàn)槊看卧儐?wèn)都是 [1, x] 的區(qū)間)而且詢問(wèn)主要慢在合并區(qū)間的操作上...不過(guò)這似乎可以一次完成。即先找到最左邊的合法線段况既,然后對(duì)于該線段的兄弟節(jié)點(diǎn)这溅,嘗試將其左邊部分二分地合并到可行解尾部并更新答案。這樣修改以后單次二型操作的理論復(fù)雜度也下降到 O(logn)棒仍。然后剪了剪枝就過(guò)了...

代碼

總復(fù)雜度為 O((n + q) × logn)

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define FOR(i, a, b) for (int (i) = (a); (i) <= (b); (i)++)
#define ROF(i, a, b) for (int (i) = (a); (i) >= (b); (i)--)
#define REP(i, n) FOR(i, 0, (n)-1)
#define sqr(x) ((x) * (x))
#define all(x) (x).begin(), (x).end()
#define reset(x, y) memset(x, y, sizeof(x))
#define uni(x) (x).erase(unique(all(x)), (x).end());
#define BUG(x) cerr << #x << " = " << (x) << endl
#define pb push_back
#define eb emplace_back
#define mp make_pair
#define _1 first
#define _2 second

const int maxn = 212345;

struct Seg {
    int l, r, lv, rv;
} T[maxn << 2];

vector<int> G[maxn];

int n, q, clk, st[maxn << 1][19], in[maxn], out[maxn], dep[maxn], w[maxn];

void dfs(int u, int p) {
    in[u] = ++clk;
    st[clk][0] = u;
    for (int v : G[u]) {
        if (v == p) continue;
        dep[v] = dep[u] + 1;
        dfs(v, u);
        st[++clk][0] = u;
    }
    out[u] = clk;
}

void rmq_init() {
    FOR(i, 1, clk) FOR(j, 1, 18) {
            st[i][j] = st[i][j - 1];
            int val = i - (1 << j - 1);
            if (val > 0 && dep[st[val][j - 1]] < dep[st[i][j]])
                st[i][j] = st[val][j - 1];
        }
}

inline int rmq(int l, int r) {
    int val = floor(log(r - l + 1) / log(2));
    int u = st[l + (1 << val) - 1][val], v = st[r][val];
    return dep[u] < dep[v] ? u : v;
}

inline int lca(int u, int v) {
    if (in[u] > in[v]) swap(u, v);
    return rmq(in[u], in[v]);
}

inline bool between(int e1, int e2, int m) {
    int a = lca(e1, e2);
    return lca(a, m) == a && (lca(e1, m) == m || lca(e2, m) == m);
}

inline pii get(const int *buff) {
    if (between(buff[0], buff[1], buff[2]) && between(buff[0], buff[1], buff[3])) 
        return mp(buff[0], buff[1]);
    if (between(buff[0], buff[2], buff[1]) && between(buff[0], buff[2], buff[3])) 
        return mp(buff[0], buff[2]);
    if (between(buff[0], buff[3], buff[1]) && between(buff[0], buff[3], buff[2])) 
        return mp(buff[0], buff[3]);
    if (between(buff[1], buff[2], buff[0]) && between(buff[1], buff[2], buff[3])) 
        return mp(buff[1], buff[2]);
    if (between(buff[1], buff[3], buff[0]) && between(buff[1], buff[3], buff[2])) 
        return mp(buff[1], buff[3]);
    if (between(buff[2], buff[3], buff[0]) && between(buff[2], buff[3], buff[1])) 
        return mp(buff[2], buff[3]);
    return mp(-1, -1);
}

void maintain(int o) {
    int lson = o << 1, rson = o << 1 | 1;
    if (T[lson].lv == -1 || T[rson].lv == -1) {
        T[o].lv = T[o].rv = -1;
        return;
    }
    if (T[lson].lv == T[lson].rv) {
        T[o].lv = T[lson].lv;
        T[o].rv = T[rson].lv;
        return;
    }
    int buff[] = {T[lson].lv, T[lson].rv, T[rson].lv, T[rson].rv};
    auto tmp = get(buff);
    T[o].lv = tmp._1;
    T[o].rv = tmp._2;
}

int solve(int o, int &p1, int &p2) {
    if (T[o].lv == -1) {
        if (T[o].l == T[o].r) return T[o].l - 1;
        int flag = solve(o << 1, p1, p2);
        return flag == T[o << 1].r ? solve(o << 1 | 1, p1, p2) : flag;
    }
    int buff[] = {p1, p2, T[o].lv, T[o].rv};
    auto check = get(buff);
    if (check._1 != -1) {
        p1 = check._1, p2 = check._2;
        return T[o].r;
    }
    if (T[o].lv == T[o].rv) return T[o].l - 1;
    int flag = solve(o << 1, p1, p2);
    return flag == T[o << 1].r ? solve(o << 1 | 1, p1, p2) : flag;
}

int query(int o) {
    while (T[o].lv == -1) o <<= 1;
    pii tmp = mp(T[o].lv, T[o].rv);
    return solve(o | 1, tmp._1, tmp._2);
}

void update(int o, int pos, int u) {
    if (T[o].l == T[o].r) {
        T[o].lv = T[o].rv = u;
        return;
    }
    int M = T[o].l + T[o].r >> 1;
    pos <= M ? update(o << 1, pos, u) : update(o << 1 | 1, pos, u);
    maintain(o);
}

void build(int o, int l, int r) {
    T[o].l = l, T[o].r = r;
    T[o].lv = T[o].rv = -1;
    if (l != r) {
        int m = l + r >> 1;
        build(o << 1, l, m);
        build(o << 1 | 1, m + 1, r);
    }
}

int main() {
    scanf("%d", &n);
    build(1, 1, 1 << 18);
    FOR(i, 1, n) {
        scanf("%d", w + i);
        w[i]++;
    }
    FOR(i, 2, n) {
        int d;
        scanf("%d", &d);
        G[i].pb(d);
        G[d].pb(i);
    }
    dfs(1, 0);
    rmq_init();
    FOR(i, 1, n) update(1, w[i], i);
    scanf("%d", &q);
    while (q--) {
        int t;
        scanf("%d", &t);
        if (t == 1) {
            int i, j;
            scanf("%d%d", &i, &j);
            update(1, w[i], j);
            update(1, w[j], i);
            swap(w[i], w[j]);
        } else {
            printf("%d\n", query(1));
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悲靴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子莫其,更是在濱河造成了極大的恐慌对竣,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榜配,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吕晌,警方通過(guò)查閱死者的電腦和手機(jī)蛋褥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)睛驳,“玉大人烙心,你說(shuō)我怎么就攤上這事》Ψ校” “怎么了淫茵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蹬跃。 經(jīng)常有香客問(wèn)我匙瘪,道長(zhǎng),這世上最難降的妖魔是什么蝶缀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任丹喻,我火速辦了婚禮,結(jié)果婚禮上翁都,老公的妹妹穿的比我還像新娘碍论。我一直安慰自己,他們只是感情好柄慰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布鳍悠。 她就那樣靜靜地躺著税娜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪藏研。 梳的紋絲不亂的頭發(fā)上敬矩,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音遥倦,去河邊找鬼谤绳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛袒哥,可吹牛的內(nèi)容都是我干的缩筛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼堡称,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瞎抛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起却紧,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桐臊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后晓殊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體断凶,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年巫俺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了认烁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡介汹,死狀恐怖却嗡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嘹承,我是刑警寧澤窗价,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站叹卷,受9級(jí)特大地震影響撼港,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜豪娜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一餐胀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瘤载,春花似錦否灾、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惩阶。三九已至,卻和暖如春扣汪,著一層夾襖步出監(jiān)牢的瞬間断楷,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工崭别, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冬筒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓茅主,卻偏偏與公主長(zhǎng)得像舞痰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诀姚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355