1. 程式人生 > 其它 >cf1620 E. Replace the Numbers

cf1620 E. Replace the Numbers

題意:

陣列初始為空。進行q次兩種操作:

  • 1 x 在陣列末尾加一個數x
  • 2 x y 把現存的所有x變成y

輸出最後的陣列。

思路:

法一:啟發式合併,線上

考慮一種暴力做法,記錄每個值 val 出現的所有位置 pos[val],操作2就把所有 pos[x] 裡的位置丟進 pos[y] 裡面。

啟發式合併,每次把小的push到大的裡面。複雜度 \(nlogn\)

int q; cin >> q; while(q--) {
    int t, x, y; cin >> t;
    if(t == 1) cin >> x, pos[x].pb(++n);
    else {
        cin >> x >> y; if(x != y) { //注意判斷
            if(pos[x].size() > pos[y].size()) //讓y是比較大的
                swap(pos[x], pos[y]); //swap的複雜度是O1的
            for(int i : pos[x]) pos[y].pb(i);
            pos[x].clear();
        }
    }
}

for(int i = 1; i < N; i++)
    for(int p : pos[i]) ans[p] = i;
for(int i = 1; i <= n; i++) cout << ans[i] << ' ';

法二:離線,倒序處理操作

注意到每次操作會對所有前面的(而不是後面的)位置造成影響。倒序處理操作,p[x] 表示 x 要變成 p[x]。

複雜度線性。注意這完全不是並查集!

const signed N = 5e5 + 3;
int p[N];

signed main() {
    iofast;
    vector<tuple<int,int,int>> query;
    int q; cin >> q; while(q--) {
        int t, x, y=0; cin >> t;
        if(t == 1) cin >> x; else cin >> x >> y;
        query.pb({t,x,y});
    }
    reverse(all(query));

    for(int i = 1; i < N; i++) p[i] = i;

    vector<int> ans;
    for(auto &[t,x,y] : query)
        if(t == 1) ans.pb(p[x]);
        else p[x] = p[y];

    reverse(all(ans));
    for(int x : ans) cout << x << ' ';
}