今天遇到一題poj2184,大概思路是01背包dp之後把符合要求的最優解統計出來。但是在解01背包的時候遇到一個問題是體積有負數,這樣在dp的過程中會遇到兩個問題:迴圈的時候超出體積的範圍;壓縮空間的時候狀態轉移方程:dp[v]=max(dp[v],dp[v-c[i]]+w[i]),c[i]為負數時v-c[i]>v,這樣按一般的迴圈的方向從大到下會重複計算。
先看第二個問題,在一般的01背包壓縮空間的時候,體積的遍曆是從大到小,因為dp[v]=max(dp[v],dp[v-c[i]]+w[i]),當前的dp[v]只取決於比自己小的dp[v-c[i]],所以從大到小遍曆時每次dp[v-c[i]]和dp[v]都是上一次的狀態。
如果體積為負v-c[i]>v,從大到小遍曆dp[v-c[i]]是當前物品的狀態,不是上一個,這樣就會出錯,解決的辦法是從小到大遍曆。
針對第一個問題,在處理的時候將整個數軸平移,使得原來所有可能的情況都為正。
例如這題,首先計算出資料的範圍:
一共100組數,從-1000到1000,那麼體積的範圍就是-100*1000到100*1000。平移之後我們要處理的資料範圍就在0到200000,新的原點變成100000。
初始化變成:
for(int i=0;i<=200000;i++) dp[i]=-INF; dp[100000]=0;
迴圈變成:
for(int i=1;i<=n;i++) { if(s[i]>0) { for(int v=200000;v>=s[i];v--)//從可能的最大值到最小值 { if (dp[v-s[i]]>-INF) dp[v]=max(dp[v],dp[v-s[i]]+f[i]); } } else { for(int v=0;v-s[i]<=200000;v++) { if (dp[v-s[i]]>-INF) dp[v]=max(dp[v],dp[v-s[i]]+f[i]); } } }
計算結果要從100000開始遍曆,因為100000相當於原來的0
int nMax=0; for(int v=100000;v<=200000;v++) if(dp[v]>=0) nMax=max(nMax,dp[v]+v-100000);
這題還有一種解決方案:把物品的體積全部加1000,使它們都大於0,然後dp的時候用一個數組記錄dp[i][v]時多加了幾個1000,最後在結果裡減去就行了。這個做法有個需要注意的地方是狀態轉移變成:
for(int i=1;i<=n;i++) for(int v=sum;v>=s[i];v--)//tot記錄多加的1000 if(dp[v]-tot[v]*1000 < dp[v-s[i]]+f[i]-(1+tot[v-s[i]])*1000) { dp[v]=dp[v-s[i]]+f[i]; tot[v]=tot[v-s[i]]+1; }
而不是
if(dp[v]<dp[v-s[i]]+f[i]) { dp[v]=dp[v-s[i]]+f[i]; tot[v]=tot[v-s[i]]+1; }
因為這時的最大值是某個v+dp[v]-tot[v]*1000,v一定的情況下dp[v]-tot[v]*1000表示的才是最大值,雖然這個最大值的意義不好理解。。
這題的第二種解法只是看結題報告粗淺的理解下,動態規劃真是非常神奇。。。。