คำอธิบายวิธีทำพร้อม code สำหรับข้อ toi21_tech_sprites

สรุปโจทย์

มี Graph อันนึงมี 1n1061 \leq n \leq 10^6 nodes (บ้านของ Tech-Sprites ที่ ii)
มี 1m31061 \leq m \leq 3 \cdot 10^6 edges
แต่ละ node ii มีค่า 2 ค่าคือ: a[i],b[i]a[i], b[i]

ใน connected-component (connected-component คือเซตของ node ที่ node ทุกตัวในเซตสามารถไปหากันได้โดยใช้ edges ที่ให้มา และไม่มี node อื่นภายนอกเซตที่สามารถเชื่อมถึงเซตนี้ได้)
เมื่อมีภารกิจสำคัญ ตัว Tech-Sprites ที่อยู่ใน connected-component จะออกมาตามลำดับ โดยตัวที่มีค่า a[i]a[i] น้อยกว่าจะออกมาก่อน
แต่ถ้า a[i]a[i] เท่ากัน b[i]b[i] น้อยกว่าจะออกมาก่อน

จงหาว่าต้องเพิ่ม edge อย่างน้อยกี่ edge เพื่อให้ Tech Sprites สามารถออกมาตามลำดับที่ถูกต้องได้
(จะออกมาทีละ connected-component และเรียงจาก a[i]a[i] ก่อน แล้ว b[i]b[i])

ตัวอย่าง

กรณีนี้ไม่จำเป็นต้องเพิ่ม edge:

example1

แต่กรณีนี้ต้องเพิ่ม edge อย่างน้อย 1 เส้น:

example2

โดยสามารถเพิ่มเส้นเชื่อม node 232-3 แบบนี้ได้:

example3

Prerequisites

วิธีทำ

  1. เก็บข้อมูล a[i],b[i],ia[i], b[i], i ไว้ใน array แล้ว sort ตามลำดับการออก (คือเรียงตาม a[i],b[i]a[i], b[i])
  2. ใช้ DSU (Disjoint Set Union) เพื่อหา connected-components
  3. map index ตั้งต้นของทุกๆ node ไปยังลำดับที่ถูก sort แล้ว
  4. เราจะ loop จาก 11 ถึง N1N - 1 โดยเราจะเก็บ array ไว้ 2 อัน ชื่อว่า
    • sz[]sz[] โดย sz[i]sz[i] จะเก็บว่า connected-component ที่มี ii เป็น root มี node ใน connected-component กี่ตัว
    • cnt[]cnt[] โดย cnt[i]cnt[i] จะเก็บว่า ตั้งแต่ loop มา มี tech-sprites ที่มี root อยู่ที่ node ii รวมทั้งหมดกี่ตัว (รวมตัวที่เราเชื่อมมาใหม่ด้วย)
  5. ถ้าหากว่าจำนวนตัวที่อยู่ใน root ของตัวปัจจุบันยังมีจำนวนตัวไม่ครบ (sz[dsu(i)](sz[dsu(i)] != cnt[dsu(i)])cnt[dsu(i)]) เราจะต้องตรวจว่าตัวถัดไปในลำดับมี root เป็นตัวเดียวกันหรือไม่ ถ้าไม่ ต้องเชื่อมด้วย edge เพิ่ม (unite node ii และ i+1i + 1 ด้วย) และเพิ่มค่า ansans ไป 1

ภาพแสดงตัวอย่าง array ที่ถูก sort แล้ว:

sorted_array

และภาพแสดงตำแหน่งที่เราตรวจสอบเงื่อนไขในข้อ 5 แล้วไม่ตรงเงื่อนไข จึงจะต้องเพิ่มเส้นเชื่อมเข้าไป:

extra_edges

Code

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 1e6 + 5, M = 3e6 + 5;

int n, m, par[N], sz[N], cnt[N], mp[N];
tuple<int, int, int> a[N];

int dsu(int a) {
    return par[a] = (a == par[a] ? a : dsu(par[a]));
}

void unite(int u, int v) {
    int uu = dsu(u), vv = dsu(v);
    if (uu != vv) {
	if (sz[uu] < sz[vv]) swap(uu, vv);
        sz[vv] += sz[uu];
        cnt[vv] += cnt[uu];
        par[uu] = vv;
    }
}

int32_t main() {
    cin.tie(NULL)->sync_with_stdio(false);
    for (auto &e : sz) e = 1;
    iota(par, par + N, 0ll);

    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        auto &[x, y, idx] = a[i];
        cin >> x >> y;
        idx = i;
    }

    sort(a + 1, a + 1 + n);

    for (int i = 1; i <= n; i++) {
        auto &[x, y, idx] = a[i];
        mp[idx] = i;
    }

    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        unite(mp[u], mp[v]);
    }

    int ans = 0;
    for (int i = 1; i < n; i++) {
        cnt[dsu(i)]++;
        if (cnt[dsu(i)] == sz[dsu(i)]) continue;
        if (dsu(i + 1) != dsu(i)) {
            ans++;
            unite(i, i + 1);
        }
    }

    cout << ans;
}

Total Time Complexity: O(n)O(n log n)

หากมีข้อสงสัย comment ไว้ใต้ post ได้เลยนะครับ 🙇‍♂️🙇‍♂️
ศึกษาโจทย์เพิ่มเติมได้ที่ Fast X Fourier