1. 程式人生 > 實用技巧 >揹包學習之揹包九講

揹包學習之揹包九講

做了一些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]);

例題:P20142020icpc南京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揹包的時候應當倒著求。