快轉到主要內容

位元枚舉

目錄

簡介
#

位元枚舉是一種枚舉的技巧,通常用來枚舉所有的子集合。在這個技巧中,我們將集合中的元素用二進位表示,然後對二進位進行枚舉。

例如,對於集合 \({1, 2, 3}\),我們可以用 \(3\) 個位元來表示,其中第 \(i\) 個位元為 \(1\) 表示元素 \(i\) 在子集合中,為 \(0\) 表示元素 \(i\) 不在子集合中。這樣,我們就可以用 \(000\), \(001\), \(010\), \(011\), \(100\), \(101\), \(110\), \(111\) 來表示所有的子集合。

實作方式
#

首先我們需要一個 \(mask\) 變數,用來儲存目前的子集合,雖然 \(mask\) 是一個整數,但我們可以將其視為一個二進位數字來操作。

如果要枚舉 \(000\) 到 \(111\),實際上整數就是 \(0\) 到 \(2^n-1\),也就是 \(0\) 到 \(7\)。因此,我們可以用以下的迴圈來枚舉所有的子集合。

for (int mask = 0; mask < (1 << n); mask++) {
    
}

接著,我們可以用以下的方法來判斷第 \(i\) 個元素是否在子集合中。

if (mask & (1 << i)) {
    // 第 i 個元素在子集合中
}

整個程式碼如下:

for (int mask = 0; mask < (1 << n); mask++) {
    for (int i = 0; i < n; i++) {
        if (mask & (1 << i)) {
            // 第 i 個元素在子集合中
        } else {
            // 第 i 個元素不在子集合中
        }
    }
}

這樣,我們就可以用位元枚舉的方式來枚舉所有的子集合了!

例題
#

給定一個長度為 \(n\) 的數列,從中選出一些數字,使得這些數字的和儘可能接近且不超過 \(k\),請你輸出最接近且不超過 \(k\) 的和。

根據上面介紹的方法,我們可以用以下的程式碼來解決這個問題。

for (int mask = 0; mask < (1 << n); mask++) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        if (mask & (1 << i)) {
            sum += a[i];
        }
    }
    if (sum <= k) {
        ans = max(ans, sum);
    }
}
Piau 的筆記本
作者
Piau 的筆記本
希望我寫下來的東西能夠長久的記在我的腦中