分治與動態規劃(3種揹包問題)
動態規劃、分治法和貪心法都是利用求解子問題,而後利用子問題求解更上層問題,最終獲得全域性解決方案的方法。但是三者的應用場景和性質卻存在著極大的不同:
1. 分治法
分治法的精髓:
分–將問題分解為規模更小的子問題;
治–將這些規模更小的子問題逐個擊破;
合–將已解決的子問題合併,最終得出“母”問題的解;
很容易將分治法與動態規劃相混淆,但兩者卻有著本質上的差異。分治法採用的是遞迴的思想來求解問題,兩個分解的子問題獨立求解,其之間無任何的重疊。而上一層問題只需要對兩個子問題進行一定的合併即可得到答案。即s(t)= s(sub1)+s(sub2)
,而s(sub1)
和s(sub2)
之間無任何重疊。
1.1 使用分治法求陣列中的最大值
函式將陣列a[1],……,a[r]分成兩部分,分別求出每部分的最大元素(遞迴地),並返回較大的那一個作為整個陣列的最大元素。
int a[];
...
int max(int l,int r) {
if (l==r) return a[l];//如果只有一個元素,就返回它
int m=(l+r)/2;
int u = max(l,m);
int v = max(m+1,r);
return (u>v) ? u : v;
}
2. 動態規劃
該種方法較為複雜,但十分有用和高效,其核心性質是當前問題的答案s(t)並不能單獨由s(t-1)求得,還有可能需要使用到s(1)...s(t-1)
動態規劃分解後的子問題相互間有聯絡,有重疊的部分。
2.1 01揹包問題
01揹包問題:
一個揹包總共可以裝下重量為W的東西,現在有N個物品,第i個物品的重量為weight[i],其價值為value[i],現在往揹包裡面裝東西,希望能夠使得揹包裝下的物品的總價值最大。
DP要求先找出子問題,我們可以這樣考慮:在物品比較少,揹包容量比較小時怎麼解決?用一個數組dp[i][j]表示,在只有i個物品,容量為j的情況下揹包問題的最優解,那麼當物品種類變大為i+1時,最優解是什麼?第i+1個物品可以選擇放進揹包或者不放進揹包(這也就是0和1),假設放進揹包(前提是放得下),那麼dp[i+1][j]=dp[i][j-weight[i+1]]+value[i+1]
f[i+1][j]=f[i][j]
。 這就得出了狀態轉移方程:
f[i+1][j]=max(f[i][j],f[i][j-weight[i+1]+value[i+1])
。 完整程式碼:
#define V 1500
int dp[10][V];//全域性變數,自動初始化為0
int weight[10];
int value[10];
#define max(x,y) (x)>(y)?(x):(y)
int main() {
int N,M;
cin>>N;//物品個數
cin>>M;//揹包容量
for (int i=1;i<=N; i++) {
cin>>weight[i]>>value[i];
}
for (int i=1; i<=N; i++)
for (int j=1; j<=M; j++) {
if (weight[i]<=j)
dp[i][j] = max(f[i-1][j],f[i-1][j-weight[i]]+value[i]);
else
dp[i][j] = dp[i-1][j];
}
cout<<dp[N][M]<<endl;
}
可以將dp[i][j]這個二維陣列優化成一維陣列。
另外,還有多重揹包和完全揹包問題,可以見連結。
優化,以及01揹包和完全揹包問題的區別
有趣的是,當我們將二維陣列優化成一維陣列時,01揹包和完全揹包問題的解法幾乎完全一樣,只有一句需要改變。
01揹包的解:
int main() {
int n,m;
while(cin>>n>>m) {
vector<int> weight(n+1,0);//物品的重量
vector<int> value(n+1,0);//物品的價值
vector<int> dp(m+1,0);//一維陣列,存放用n個產品裝容量為i的揹包的解
for(int i=0;i<n;i++)
cin>>weight[i+1]>>value[i+1];//輸入
for(int i=1;i<=n;i++)
//這裡是逆序
//用j>=weight[i]作為判斷條件,省略了一個if語句
for(int j=m;j>=weight[i];j--) {
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
cout<<dp[m]<<endl;
}
return 0;
}
這裡,只用到了一維陣列,但是通過採用逆序的方法,使得效果是相同的。
然後是完全揹包問題的解,只需要將上面的逆序改為順序:
for(int i=1;i<=n;i++)
//這裡變成了順序
//這裡設j從weight[i]開始,省略了一個if語句
for(int j=weight[i];j<=m;j++)
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);