快轉到主要內容

CSES-2163 Josephus Problem II

目錄


題目連結:https://cses.fi/problemset/task/2163

題意
#

此為「約瑟夫問題」的進階版。
\( n \) 個人圍成一圈,編號為 1 到 \( n \)。
規則改為:跳過 \( k \) 個人,然後淘汰下一個人(即每數到第 \( k+1 \) 個人進行淘汰)。
被淘汰者出局後,圈子縮小,由下一位繼續重新計算跳過 \( k \) 個人。
需要依序輸出被淘汰的人的編號。

思路
#

若使用 Queue 或陣列直接模擬每次「跳過 \( k \) 個人」的過程,時間複雜度會過高導致 TLE。
為了高效處理,可使用 C++ 的 __gnu_pbds(Policy Based Data Structure)紅黑樹來加速模擬。

PBDS 提供的 tree 擁有以下關鍵特性:

  1. find_by_order(k):以 \(\mathcal{O}(\log N)\) 找出樹中「第 \( k \) 小」的元素。
  2. order_of_key(x):查詢樹中「嚴格小於」\( x \) 的元素數量。

本題將使用 find_by_order
首先將 1 到 \( n \) 插入 PBDS 紅黑樹中,並使用變數 now 記錄當前淘汰位置的「相對排名(0-based)」。
每次淘汰流程如下:

  1. 利用圓環的特性,下一次淘汰的位置排名為 (now + k) % 剩餘人數
  2. 透過 find_by_order(now) 取出該排名的編號並輸出,最後使用 erase 將其從樹中移除。
  3. 移除後,樹內剩餘元素的排名會自動遞補,因此 now 自然指向下一個要開始計算的位置。

此方法每次尋找與刪除皆只需 \(\mathcal{O}(\log N)\),整體時間複雜度為 \(\mathcal{O}(N \log N)\)。

程式碼
#

#include <bits/stdc++.h>
// 引入 PBDS 所需的標頭檔
#include <bits/extc++.h>
using namespace std;
// 使用 pbds 的命名空間
using namespace __gnu_pbds;

#define pb push_back
#define fi first
#define se second
#define INF LONG_LONG_MAX/1000
#define WA() cin.tie(0)->sync_with_stdio(0)
#define all(x) (x).begin(), (x).end()
#define int long long
#define PII pair<int, int>

// 宣告一個支援 O(log N) 查詢「第 K 大小」的紅黑樹
signed main() { WA();
    int n, k; cin >> n >> k;
    
    // 定義 pbds 的 tree 型別,這是一棵儲存 int 的紅黑樹
    tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> t;
    
    // 最初將 1 到 n 依序插入紅黑樹中
    for (int i = 1; i <= n; i++) t.insert(i);
    
    // now 記錄目前淘汰的「相對排名(0-based)」
    int now = 0; 
    
    // 持續執行直到所有人皆被淘汰
    while (t.size()) {
        // 取得跨越 k 人後目標的位置排名
        now = (now+k)%t.size();
        
        // 透過 find_by_order 取出該排名的元素
        cout << *t.find_by_order(now) << ' ';
        
        // 從樹中將該元素刪除
        t.erase(t.find_by_order(now));
    }
}
Piau 的筆記本
作者
Piau 的筆記本
希望我寫下來的東西能夠長久的記在我的腦中