常用小技巧之折半列舉(超大揹包例項)

2021-10-25 06:05:32 字數 2708 閱讀 5084

有時候,問題的規模比較大,外面無法列舉所有元素的組合,但能列舉一半或者一部分的組合。此時,將問題拆分成兩半或幾部分後分別列舉,再合併他們,這一方法往往非常有效。

舉個例子

例題:給定各有n個整數的四個數列a,b,c,d。要從每個數列中個取出1個數,使四個數的和為0,求出這樣組合的個數。當乙個組合中有多個相同的數字時,把他們當不同的數字看待。(poj 2785)

思路:從四個數列中選擇的話共有n4種情況,所以全部列舉一遍肯定不行,不過將他們對半分成ab和cd再考慮,就可以快速的解決了。從兩個數列中選擇的話就只有n2種組合,可以進行列舉。從a、b中選出a,b後,為使總和為0,所以應使c+d = -a-b。因此列舉c、d中取數字,然後計算所有情況的和,將其排個序,這樣就可以二分搜尋了,時間複雜度是o(n2logn)。

**模板:

#include

#include

using

namespace std;

const

int n =

4010

;long

long n,a[n]

,b[n]

,c[n]

,d[n]

,sum[n*n]

,ans;

//sum是c和d數列所有和的陣列

void

solve()

} cout << ans << endl;

}int

main()

lower_bound和upper_bound函式具體可以參考此文章

我們現在有體積和價值分別為v和w的n個物品。現在從這些物品中選出體積不超過m的物品放進揹包中,求所有挑選方案中價值總和的最大值。

(1 <= n <= 40,1 <= wi,vi

<= 1015,1 <= m <= 1015)

思路:這就是乙個很基礎的01揹包問題,但我們知道,01揹包的時間複雜度是o(nm),n是物品數,m是揹包體積,因為我們用了兩層迴圈一層迴圈物品,一層迴圈體積。但此處的揹包體積非常大,達到了1015,若再用nm的複雜度肯定會超時,所以針對這個體積超大的揹包,我們應該利用n比較小這一特點,去考慮問題。

最簡單的想法是列舉所有選物品的情況,但挑選物品的方法共有2n種,所以不能直接列舉,但我們可以拆成兩部分,分別列舉,220的複雜度還是可以接受的。

當我們把前半部分所有情況列舉出來後,如何判斷後面一部分呢?記前半部分一種選取方法對應的體積和價值總和分別為v1和w1。這樣在後半部分尋找總體積v2 <= m-v1時使v2達到最大就好了。我們要思考從所有列舉的(v2,w2)的集合中高效的找到max,因此我們可以排除所有的v2[i] < v2[j]且w1[i] > w1[j]的 j的情況,這一點可以按照v2,w1排序篩出來。得到的所有元素都滿足v2[i] < v2[j]且w2[i] < w2[j](如下圖),要計算max只需要找到滿足v2 <= m-v1的最大i就可得到,這裡可以用二分完成。所以總的時間複雜度是o(2(n/2)n)。

如下圖,若某種選法的總體積大於前乙個但總價值還小於前乙個(紅圈內的情況),那我們肯定不選擇這種選法,所以我們直接刪去這種選法來簡化計算。

**詳解:

#include

#include

#include

using

namespace std;

typedef

long

long ll;

const

int n =50;

const ll inf =

0x3f3f3f3f

;ll n,m,v[n]

,w[n]

;vector

> p;

//p存第一次列舉出的所有的v和w

void

solve()

} p.

push_back()

;//存入p陣列

}sort

(p.begin()

,p.end()

);//對p排序

//篩除必不可能選的元素

int num =1;

//num來存下所有可以選的物品

for(

int i =

1;i <

1<< nn;i++

)//迴圈所有物品(此時已經按體積從小到大排好序了)

if(p[num-1]

.second < p[i]

.second)

//如果當前的價值大於剛才存的最後乙個的價值,就把這個存進去

p[num++

]= p[i]

;//存進去

//列舉後半部分

ll ans =0;

for(

int i =

0;i <

1<<

(n-nn)

;i++

)//迴圈後半部分的所有情況}if

(v2 <= m)

//如果只選取後半部分的體積已經超過了m就不用再判斷了

} cout << ans << endl;

//輸出答案即可

}int

main()

MySQL SQL之常用小技巧

一 返回插入新記錄的id insert into 表名 字段列表 output inserted.id values 字段值列表 二 條件式排序 select from 表名 order by case when 欄位名 某值 then 1 else 0 end asc desc 三 清空表 tru...

C 的列舉小技巧

列舉是什麼呢?列舉是乙個值型別,包含一組命名的常量,如以下的 public enum color 這裡是最簡單的定義列舉的方式,沒有設定任何的東西,所以預設情況下,enum的型別是int型的,並且是從0開始往下遞增地,但是我們可以修改以上說的東西,比如以下 public enum color sho...

列舉平方數 小技巧

給出長度為n的序列a,求有多少對數對 i,j 1 i j n 滿足 ai aj 為完全平方數。第一行乙個整數 n 1 n 105 第二行 n 個整數 ai 1 ai 105 輸出乙個整數,表示滿足上述條件的數對個數。示例1 複製3 1 3 6 複製2 滿足條件的有 1,2 2,3 兩對。有幾個需要注...