CF2034 A-E題解

XYukari發表於2024-12-01

A. King Keykhosrow's Mystery

題意可以轉化為存在 \(k_1,k_2\) 使得 \(m=a\times k_1+n = b\times k_2 +n\)。消去餘數 \(n\) 得到 \(a\times k_1=b\times k_2\),即 \(a,b\) 的公倍數。所以最小的 \(m\) 就是 \(a,b\) 的最小公倍數,餘數為 0。最小公倍數的計算方法是 \(\text{lcm}(a,b)=\dfrac{a\times b}{\gcd(a,b)}\)

B. Rakhsh's Revival

考慮貪心的處理:逐位掃過去,維護一個變數 \(cnt\) 記錄當前連續 \(0\) 的個數,遇到 \(1\) 就清空。因為提前操作並不能減少操作次數,所以我們僅當 \(cnt=m\) 時進行操作,把從這一位開始連續 \(k\) 個賦值成 \(1\)(實現時不用真的賦值,直接下標加 \(k\) 即可)。

int cnt = 0, ans = 0;
for (int i = 0; i < n; i++)
    if (s[i] == '1') { cnt = 0; continue; }
    else if (++cnt == m) i += k - 1, ans++, cnt = 0; // 因為for還要i++,這裡只加k-1; 記得操作後cnt也要清零
cout << ans << '\n';

C. Trapped in the Witch's Labyrinth

對於 \(n\times m\) 個格子,可以分 4 種情況考慮:

  • 連通的一片 ?:把連通的一片 ? 拆分成一個個矩形。除了 \(1\times 1\) 的矩形,都可以每行構造成 >>>>>< ,一定走不出去;\(1\times 1\) 的矩形直接指向別的 ? 即可。
  • 孤立的一個 ?:它的周圍沒有別的 ? 了,當且僅當存在確定方向的格子指向它時,它走不出去(反向指對方即可)。注意多個格子指向它時也不成問題,只需要反指任意一個,別的格子都會被導向這個 >< 的迴圈裡。
  • 方向確定,一定能走出去:根據固定的方向最終走到了邊界,或者已經確定的“能走出去的格子”。它們對答案沒有貢獻,dfs 時順帶標記掉就好。
  • 方向確定,走不出去:最終指向了 ?,走不出去。

所以從每個格子出發 dfs,如果是 ? 則把連通塊的大小計入答案(大小為 1 即孤立時先不計入,插入 set 中以備查詢);如果確定了方向則判斷是否會走出,把走不出的連通塊大小加入答案。最後對於所有的孤立點檢查上下左右有沒有走不出去的,有的話答案加 \(1\)

void solve(int test_case) {
    int n, m, siz, ans = 0;
    cin >> n >> m;
    vs s(n);
    vector<vc> vis(n, vc(m)); // 記錄格子是否到達過
    vector<vc> out(n, vc(m)); // 記錄格子是否會走出
    rep(i, 0, n - 1) cin >> s[i];
    set<pii> single; // 記錄孤立點

    const int dx[4] = {-1, 1, 0, 0};
    const int dy[4] = {0, 0, -1, 1};
    map<char, int> mv = {{'U', 0}, {'D', 1}, {'L', 2}, {'R', 3}};

    auto inside = [&](int x, int y) -> bool {
        return x >= 0 && x < n && y >= 0 && y < m;
    };

    auto dfs1 = [&](auto&& dfs1, int x, int y) -> void { // 搜?的連通塊
        vis[x][y] = 1;
        siz += 1;
        rep(i, 0, 3) {
            int tx = x + dx[i];
            int ty = y + dy[i];
            if (inside(tx, ty) && s[tx][ty] == '?' && !vis[tx][ty]) {
                dfs1(dfs1, tx, ty);
            }
        }
    };

    auto dfs2 = [&](auto&& dfs2, int x, int y) -> bool { // 搜方向確定的連通塊
        vis[x][y] = 1;
        siz += 1;
        int d = mv[s[x][y]];
        int tx = x + dx[d];
        int ty = y + dy[d];
        if (!inside(tx, ty) || out[tx][ty]) {
            return out[x][y] = 1;
        }
        if (vis[tx][ty] || s[tx][ty] == '?') return 0;
        return out[x][y] = dfs2(dfs2, tx, ty);
    };

    rep(i, 0, n - 1) rep(j, 0, m - 1) {
        if (vis[i][j]) continue;
        siz = 0;
        if (s[i][j] == '?') {
            dfs1(dfs1, i, j);
            siz == 1 ? single.emplace(i, j), 0 : ans += siz;
        } else {
            ans += dfs2(dfs2, i, j) ? 0 : siz;
        }
    }
    for (auto [x, y] : single) {
        rep(i, 0, 3) {
            int tx = x + dx[i];
            int ty = y + dy[i];
            if (inside(tx, ty) && !out[tx][ty]) {
                ans += 1;
                break;
            }
        }
    }
    cout << ans << '\n';
}

D. Darius' Wisdom

用兩個 set 記錄 \(1,2\) 出現位置的下標,然後從後往前遍歷,集合維護當前遍歷位置前面的數(如果當前走到了 \(1,2\) 就從集合中刪掉)。對於 \(0\) 來說,如果前面還有 \(2\),就從集合中找到一個 \(1\) 進行交換,再找到一個 \(2\) 進行交換;前面沒有 \(2\) 了,就找到 \(1\) 進行交換;\(1\) 都沒有則退出。對於 \(1\) 來說,如果前面有 \(2\) 就進行交換,否則跳過。次數顯然小於 \(n\) 次。

void solve(int test_case) {
    int n;
    cin >> n;
    vi a(n);
    vector<pii> ans;
    set<int> s1, s2;
    rep(i, 0, n - 1) {
        cin >> a[i];
        if (a[i] == 1) {
            s1.insert(i);
        } else if (a[i] == 2) {
            s2.insert(i);
        }
    }
    dep(i, n - 1, 0) {
        if (a[i] == 2) {
            s2.erase(i); // 當前的2遍歷過了,刪掉
            continue;
        }
        if (a[i] == 1) {
            if (s2.empty()) {
                s1.erase(i); // 當前的1遍歷過了,刪掉
                continue;
            }
            int k = *s2.begin();
            s2.erase(k);
            swap(a[i], a[k]); // 交換1,2
            s1.erase(i);
            s1.insert(k); // 更新1的位置
            ans.emplace_back(i, k);
            continue;
        } // 下面 a[i] == 0
        if (s2.empty()) {
            if (s1.empty()) break; // 都沒有,交換結束
            int k = *s1.begin();
            s1.erase(k); // 沒有2了,1的位置就確定了,直接刪掉
            swap(a[i], a[k]);
            ans.emplace_back(k, i);
        } else {
            int k1 = *s1.begin();
            int k2 = *s2.begin();
            s2.erase(k2); // 2的位置確定了,可以刪掉
            s1.erase(k1);
            a[i] = 2, a[k1] = 0, a[k2] = 1;
            s1.insert(k2); // 更新1的位置
            ans.emplace_back(i, k1);
            ans.emplace_back(i, k2);
        }
    }
    cout << ans.size() << '\n';
    for (auto [i, j] : ans) {
        write("%d %d\n", i + 1, j + 1);
    }
}

E. Permutations Harmony

首先要判定一下 \(k\le n!\),這個只在 \(n\) 很小的情況下有可能超過,問題不大。然後注意到 \(k\) 為偶數時一定可以做,只需要找到 \(\dfrac{k}{2}\) 個對稱的排列即可,如 1234|4321, 2134|3421 等。\(k\) 為奇數時,需要進一步討論:

  • \(k=1\),只在 \(n=1\) 的情況下有解;\(k=n!-1\) 一定無解。
  • \(n\) 為偶數時一定無解,因為每個 \(i\) 的和相同,總和為 \(\dfrac{n(n+1)k}{2}\),每個 \(i\) 的和為 \(\dfrac{(n+1)k}{2}\)\(n\) 為偶數 \((n+1)k\) 除不開。
  • \(n\) 為奇數時,\(k\) 可以拆成 \(3\)\(k-3\),後者為偶數,前者有貪心構造方案如圖。

image