位元枚舉

罕見

簡介

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

例如,對於集合 {1,2,3}\{1, 2, 3\},我們可以用 33 個位元來表示,其中第 ii 個位元為 11 表示元素 ii 在子集合中,為 00 表示元素 ii 不在子集合中。這樣,我們就可以用 000000, 001001, 010010, 011011, 100100, 101101, 110110, 111111 來表示所有的子集合。

實作方式

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

如果要枚舉 000000111111,實際上整數就是 002n12^n-1,也就是 0077。因此,我們可以用以下的迴圈來枚舉所有的子集合。

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

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

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 個元素不在子集合中
        }
    }
}

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

例題

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

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

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);
    }
}