# Josephus Problem II
這題是排在CSES裡的Sorting and Searching分類裡
解法大多是使用Binary-Index-Tree(或稱Fenwick Tree)
BIT是個剛開始學/會覺得很靠北的演算法,它的原理有點通靈
這就來為各位講解一下CSES裡的Josephus Problem II
# **範例程式**
```
#include <bits/stdc++.h>
using namespace std;
struct Fenwick {
vector<int> bit;
int n;
Fenwick(int n) : n(n), bit(n+1, 0) {}
void add(int idx, int val) {
for (; idx <= n; idx += idx & -idx) bit[idx] += val;
}
int sum(int idx) {
int res = 0;
for (; idx > 0; idx -= idx & -idx) res += bit[idx];
return res;
}
int kth(int k) { // 找第 k 個活著的人
int idx = 0;
int mask = 1 << (31 - __builtin_clz(n));
for (; mask; mask >>= 1) {
int next = idx + mask;
if (next <= n && bit[next] < k) {
k -= bit[next];
idx = next;
}
}
return idx + 1;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
long long k;
cin >> n >> k;
Fenwick fw(n);
for (int i = 1; i <= n; i++) fw.add(i, 1);
int pos = 1; // 從第 1 個小孩開始
for (int rem = n; rem > 0; rem--) {
long long step = k % rem + 1; //要走幾步才會到指定位置
int curr_sum = fw.sum(pos - 1); //需要[1, pos-1]的總和
int target_sum = (curr_sum + step) % rem;
if (target_sum == 0) target_sum = rem;
int idx = fw.kth(target_sum);
cout << idx << (rem == 1 ? '\n' : ' ');
fw.add(idx, -1);
pos = idx;
}
}
```
## 將n固定為8的範例
//* ------------------ 最小示範 ------------------
1) 建立 n=8 的 Fenwick
2) 將 a[i] 全部設為 1(代表 8 個活著)
3) 查 sumPrefix(7) 與區間和
4) 刪掉位置 3(add(3,-1))
5) 用 kth(第5個1) 反查座位
*//
```
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n = 8;
Fenwick fw(n);
// 將 a[1..n] 全設為 1
for (int i = 1; i <= n; ++i) fw.add(i, 1);
cout << "sumPrefix(7) = " << fw.sumPrefix(7) << "\n"; // 7
cout << "sumRange(3,6) = " << fw.sumRange(3, 6) << "\n"; // 4
// 刪掉位置 3(活著→死亡)
fw.add(3, -1);
cout << "after remove 3, sumPrefix(7) = " << fw.sumPrefix(7) << "\n"; // 6
// 找目前第 5 個活人是誰(原本 {1..8},去掉 3,活人是 {1,2,4,5,6,7,8},第5個=6)
int pos = fw.kth(5);
cout << "kth(5) = " << pos << "\n"; // 6
return 0;
}
```
Fenwick Tree 的「選擇性查詢」:
從最高位 bit 開始二分
⬇
累積和小於 k 的話,向右移動
⬇
最後得到的位置就是**第 k 個活著的人的編號**
每次跳 k 個人
要找第pos個小孩
把所有含pos的bit段落更新
(以上是主要操作原理,如果看不懂,建議先看後面的設定和原理)
# **設定**
我們用 alive[i] 表示 i 是否還活(活=1、死=0)。
> add(i, delta):更新位置 i 的值,加上 delta(O(log n))
>
>
> 刪掉一個人就是 add(pos, -1)
> sum(i):求 1 到 i 的前綴和(O(log n))
>
>
> 代表目前有多少人位置 ≤ i
> sumPrefix(x) = 1..x 的活人數。
> rem = 目前剩下的人數。
我們把「跳 k 個,刪下一個」轉成「以活人次序(rank)來找人」:
> 每輪的有效步數:step = (k % rem) **+ 1** (加我現在站的區塊)
> 當前「起點」的活人次序:curr = sumPrefix(pos-1)
> 目標活人次序:target = (curr + step) % rem;若為 0,用 rem
用 BIT 反查「第 target 個活人」得到實際座位 idx,刪掉他
下一輪的起點 pos = idx 的下一個還活著的人(環狀,若沒有就繞回最小活人)
**核心:我們一直在找「第 target 個活著的人」。**
sumPrefix() 用來把位置 ↔ 活人次序在 O(log n) 內互相對照(**反查用 kth**)。
# **查詢&更新原理**
### 問題1
**1) idx 是什麼?放進 BIT 的原理是什麼?初始值是什麼?**
idx 就是 bit 的索引,同時也對應到原陣列 (alive[1...i]) 的某個尾端位置。
Fenwick Tree 用一個同樣長度的陣列 bit[1..n] 來「壓縮存取」
bit[idx] 不是只存 a[idx],而是存 一段(下面第 2 點說明)。
### 問題2
**2) 「長度為 lowbit(idx) 的區段總和」是怎麼切?過程與最後值是什麼?**
lowbit(idx) 是二進位中 idx **末尾連續 0 的「對應 2^r」那一位。**
***這格管的區間長度=lowbit[這格的值]***
|分類代表⬇數字(n)➡| 1 | 2 | 3 |4 | 5 | 6 |
| -------- | -------- | -------- | -------- | -------- | -------- | -------- |
|二進制(末位有幾個連續的0)| 001 | 01**0** | 011 | 1**00** | 101 | 11**0** |
|bit[n]管理範圍| [1,1]| [1,2]| [3,3]| [1,4] | [5,5] | [5,6]|
|管理幾個數字"lowbit[n]"| 2^0 = 1| 2^1 = 2| 2^0 = 1| 2^2 = 4 | 2^0 = 1 | 2^1 = 2|
**上述lowbit():**
2^0 = 1(lowbit) “因為末尾是1,沒有0”
2^1 = 2 **“因為末尾是0,有幾個0,2就有幾次方”**
依此類推 | 以下
### 問題3
**3) 為何 bit[idx] 覆蓋的範圍是[idx - lowbit(idx) + 1, idx]?**
Fenwick Tree 的結構設計就是「每個 idx 管理一段以 idx 為結尾、長度 = lowbit(idx) 的區間」
這樣定義能保證:
所有前綴和 sumPrefix(i) 都能被幾個「不重疊」的 bit[...] 區間拼起來(下一點會用到)。
### 問題4
**4) 為何「沿著涵蓋 i 的那些區段往上/下跳」就能解決更新與查詢?**
那就需要分成「更新」與「查詢」兩件事來說:
> 函式: add(原陣列裡的第i個數, 原值+delta)
---------
#### 查詢
查詢是把 1..i 切成不重疊的段,一段一段讀值加起來
> i -= lowbit(i)
例: bit[4]&bit[6] 可拼成1~6的段落
把所有查出的bit[x]的值加總
就可以得到所要段落總和
-----
#### 更新
pos[n]更新是找所有含 n 這個值的段,把它們的值更新掉
> i += lowbit(i)
n = 8 , pos = 7 , 更新bit[7]、bit[8] (若n=8,僅這兩個bit有儲存"7"這個數字)
bit[7] = [7,7] (含7)
bit[8] = [1,8] (含7)
--------
#### 原理總結(很重要)
**它們走的方向相反:**
**查詢:i -= lowbit(i)(往回跳)**
**更新:i += lowbit(i)(往前跳)**