使用bitset最佳化0/1揹包的時空複雜度。(100320. 執行操作可獲得的最大總獎勵 II)

tanch25發表於2024-06-10

被leetcode401單週賽狠狠打敗了。就此記錄下bitset最佳化0-1揹包。利用數字的二進位制操作對整體進行轉移

問題描述

點選連結直接獲取問題描述

求解思路

狀態定義

定義\(dp[i][j]\)表示能否從前i個數中得到總獎勵j。每次轉移對前一個狀態的j有特殊要求。

狀態轉移方程

考慮第i個物品選或不選:

  • 不選第i個物品:\(dp[i][j] = dp[i-1][j]\)
  • 選擇第i個物品:\(dp[i][j] = dp[i-1][j-w[i]]\) ,需要滿足\(j \geq w[i]\)\(j - w[i] < w[i]\)
    狀態為能否從前i個數中得到獎勵,即$$dp[i][j] = dp[i-1][j]\ \cup\ dp[i-1][j-w[i]] $$
初始化方式

初始值\(dp[0][0] = true\)
所求答案為\(dp[n][j] = true\)中最大的j。

最佳化方式

對應的狀態個數為\(n^{2}\),當然是過不了當前\(n = 2 \times 10^{5}\) 的資料。需要最佳化掉其中一個維度。

  1. 由於狀態轉移中第\(i\)層一定從第\(i - 1\)層轉移過來,可以兩個陣列或者原地滾動進行最佳化。但只省下空間,總狀態數還是那麼多,沒啥用

  2. 注意到\(dp[i][j]\)一定是從\(dp[i-1][j]\)\(dp[i-1][j-w[i]]\)轉移過來,對應的\(0 \le j-w[i] < w[i]\)。也就是存在一一對應的關係,並且轉移方式為兩者取或。使用bitset對最佳化當前0/1揹包的狀態轉移。

    二進位制數表示每個狀態,二進位制數位j上的數字表示當前狀態下的j能否被取到。 對應的狀態轉移為了滿足限制條件,需要先將\(x\)中數位大於\(j\)的位置設定為0,再將當前的二進位制數\(x\)與左移\(w[i]\)位的\(x^{*}\)取或。初始狀態設定為1,代表開始只有0滿足要求。

bitset介紹

該部分內容搬運自 -Wallace-的文章

它類似於 bool 陣列,每個位置只有兩種值:0 或 1。
bitset 的實現方式是壓位,那麼一個大小為 n𝑛 的 bitset 的空間複雜度為 \(O(\frac{1}{ω}n)\)。其中 ω=32 或 64(系統位數)。

基本操作
bitset<N> f;//定義一個大小為N的bitset,下標範圍[0, N)
f.set(i);//在下標i設為1
f.reset(i);//將下標i設定為0
f.test(i);//判斷下標i處是否為1
f[i];//取值

除了建構函式,其餘操作均為\(O(1)\)

進階操作
f.set(); // 全部置為 1 
f.reset(); // 全部置為 0 
f = g; // bitset 賦值 
f &= g; // 將 f 對 g 做按位與操作 
f |= g; // 將 f 對 g 做按位或操作 
f ^= g; // 將 f 對 g 做按位異或操作 
// 以及各種位運算操作 
f.count(); // 計算 bitset 中 1 的個數

這些範圍操作都是\(O(\frac{1}{ω}n)\)的時間複雜度。bitset時空複雜度中的常數\(\frac{1}{w}\) 是bitset最佳化的關鍵。

程式碼案例

源自靈茶山艾府

bitset<100000>f{1};//初始化下標0的位置為1
for (int v : rewardValues) {
    int shift = f.size() - v;
    //bitset的區間操作帶有常數1/w
	f |= f << shift >> shift << v;
}
for (int i = rewardValues.back() * 2 - 1; ; i--) {
		//獲取最大的下標為1的位置
        if (f.test(i)) {
            return i;
        }
    }

使用bitset最佳化後,對應的時間複雜度為\((\frac{1}{w} n^{2})\) ,\(w\)對應為32或64,滿足資料範圍\(n = 2\times 10^{5}\)的要求。

相關文章