揹包學習之揹包九講
做了一些icpc題目,感受到動態規劃的重要性,在此學習揹包
參考至部落格揹包九講
揹包九講
- 01揹包問題
- 完全揹包問題
- 多重揹包問題
- 混合揹包問題
- 二維費用的揹包問題
- 分組揹包問題
- 有依賴的揹包問題
- 揹包問題求方案數
- 求揹包問題的具體方案
01完全揹包:
問題:n個物品(體積和價值分別為v[i],w[i])和一個體積為V的揹包,求揹包裝到的最大價值
狀態陣列:dp[i][j]為選前i個所用體積為j的最大價值
for(int i=1;i<=n;++i) for(int j=1;j<=V;++j) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
滾動陣列優化:
for(int i=1;i<=n;++i)
for(int j=V;j>=vi;--j)
dp[j]=max(dp[j],dp[j-v[i]])+w[i]
完全揹包:
問題:n種物品(每種無限個,體積和價值分別為v[i],w[i])和一個體積為V的揹包,求揹包最大價值
狀態陣列:dp[i][j]為選到前i種所用體積為j的最大價值
for(int i=1;i<=n;++i) for(int j=0;j<=V;++j) for(int k=1;k*v[i]<=j;++k) dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
時間優化
for(int i=1;i<=n;++i)
for(int j=0;j<=V;++j)
{
dp[i][j]=dp[i-1][j];
if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
}
滾動陣列空間優化
for(int i=1;i<=n;++i)
for(int j=V;j>=v[i];--j)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
多重揹包
問題:n種物品(每種有限個,數量,體積和價值分別為s[i],v[i],w[i])和一個體積為V的揹包,求揹包最大價值
狀態陣列:dp[i][j]為選到前i種所用體積為j的最大價值
for(int i=1;i<=n;++i)
for(int j=0;j<=V;++j)
for(int k=0;k*v[i]<=j&&k<=s[i];++k)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
空間優化
for(int i=1;i<=n;++i)
for(int j=V;j>=0;--j)
for(int k=0;k*v[i]<=j&&k<=s[i];++k)
dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
二進位制時間優化
將數量為s[i]的物品分成s[i]=1+2+4+8+...+x,然後跑01揹包,複雜度由n3優化為n2logn
程式碼略
多重揹包
問題:既有無限個的,又有單個的,又有多個的,仍然是揹包V,求最大價值
單個的可看成多重揹包的特殊狀況,然後有限個和無限個分別討論
直接上一維:dp[j]代表用j體積裝到的最大價值
for(int i=1;i<=n;++i) cin>>v[i]>>w[i]>>s[i];//當s[i]=0時,為無限個
for(int i=1;i<=n;++i)
{
if(s[i]==0)
{
for(int j=V;j>=v[i];--j) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
else
{
for(int k=1;k<=s[i];s[i]-=k,k*=2)
{
for(int k=V;j>=k*v[i];--j)
dp[j]=max(dp[j],dp[j-k*v[i]]+w[i]);
}
if(!s[i]) continue;
for(int j=V;j>=s[i]*v[i];j--)
dp[j]=max(dp[j],dp[j-s[i]*v[i]]+s[i]*w[i]);
}
}
二維費用的揹包問題
問題:n個物品(體積,重量和價值分別為v[i],m[i],w[i])和一個體積為V,限重為M的揹包,求揹包最大價值
問題:n種物品(每種有限個,數量,體積和價值分別為s[i],v[i],w[i])和一個體積為V的揹包,求揹包最大價值
加一維就行了
狀態陣列:dp[i][j]代表體積為i,載重為j時的最大價值
for(int i=1;i<=n;++i)
for(int j=V;j>=v[i];j--)
for(int k=M;k>=m[i];--k)
dp[j][k]=max(dp[j][k],dp[j-v[i]][k-m[i]]+w[i]);
分組揹包
問題:n堆物品,每堆s[i]個,每個體積和價值為v[i],w[i],和一個體積為V揹包,每堆物品只能選一個,求最大價值
分組揹包看起來沒什麼用,實際上是各種DP題的熱門考點,特別是樹形DP
一維狀態:dp[j]代表用j體積的最大價值
for(int i=1;i<=n;++i)
for(int j=V;j>=0;--j)
for(int k=1;k<=s[i];++k)
if(j>=v[k])dp[j]=max(dp[j],dp[j-v[k]]+w[k]);
例題:P2014,2020icpc南京M
有依賴的揹包問題
問題:n個物體之間存在著依賴關係,例如依賴關係是一棵樹,選擇一個節點需要選擇其父親節點
實質上就是一道赤果果的樹形DP
狀態方程:dp[x][y] 代表x節點選了體積為y的最大價值
void dfs(int s)
{
for(auto v:edge[s])
{
dfs(v);
for(int j=V;j>=v[s];--j)
for(int k=0;k<=j-v[s];++k)
dp[s][j]=max(dp[s][j],dp[s][j-k]+dp[v][k]);
}
}
第二類問題:n節點數選m點,選一個點必須選父節點,使總價值最大
狀態方程:dp[x][i] 代表x節點選擇了i個節點的最大價值,答案就是dp[1][m]
void dfs(int s)
{
for(auto v:edge[s])
{
dfs(v);
for(int i=m;i>=1;--i)
for(int j=0;j<i;++j)
dp[s][i]=max(dp[s][i],dp[s][i-j]+dp[v][j]);
}
}
揹包問題求方案數
問題:n個物品(體積和價值分別為v[i],w[i])和一個體積為V的揹包,求選取價值達到最大的方案數
只需在原來的基礎上加一個計數的陣列即可
for(int i=1;i<=n;++i)
for(int j=V;j>=v[i];--j)
{
if(dp[j-v[i]]+w[i]>dp[j])
{
dp[j]=dp[j-v[i]]+w[i];
num[j]=num[j-v[i]];
}
else if(dp[j-v[i]]+w[i]==dp[j])
num[j]+=num[j-v[i]];
}
揹包問題求出具體方案
問題:n個物品(體積和價值分別為v[i],w[i])和一個體積為V的揹包,求選取價值達到最大的方案
for(int i=1;i<=n;++i)
for(int j=V;j>=vi;--j)
dp[j]=max(dp[j],dp[j-v[i]])+w[i];
int val=V;
for(int i=n;i>=1;--i)
if(val-v[i]>=0&&dp[val-v[i]]+w[i]==dp[val])
cout<<i<<" ";
如果題目有要求字典序最小,那求解01揹包的時候應當倒著求。