最近在學DP,上週六ACM集訓隊花一整天的時間共同探討了最經典的DP--揹包問題,對這類問題研究也挺深入的,感謝各隊友及老師的講解,覺得受益匪淺!
(1)01揹包
01揹包算是最基礎的揹包問題了,意思就是共有N個物品,一個揹包。各物品的重量爲Wi,價值爲Vi,揹包能承受的最大重量爲W,求揹包能裝進去物品的最大價值!分析:其一是變量元素,可知與該結果有關係的就是重量和價值,其次是狀態轉移。那麼顯然子問題就是當前的揹包重量能裝的最大價值,用數組F[i][j]表示的話,揹包的剩餘重量爲j,i爲當前物品,F[i][j]保存的就是揹包在j容量下裝的最大價值,即把1,2,3...i-1個物品裝到容量爲j的揹包中的最大價值。對於第i個物品,有兩種選擇,放或者不放,當j<wi時,肯定是不能放的,那麼F[i][j]=F[i-1][j],若j>=Wi,放第i件物品時,剩餘揹包容量爲j-Wi,此時價值爲F[i][j]=F[i-1][j-Wi]+Vi,做價值最大的選擇,則:F[i][j]=MAX(F[i-1][j],F[i-Wi]+Vi。可由底層到頂層推,那麼F[1][W]爲最終值,代碼如下:
int DP1()
{
for(int j=W;j<=0;j++)
{
if(j<w[N])
f[N][j]=0;
else
f[N][j]=v[i];
}
for(int i=N-1;i>=1;i--)
for(int j=0;j<=W;j++)
{
if(j<w[i])
f[i][j]=f[i+1][j];
else
f[i][j]=max(f[i+1][j],f[i+1][j-w[i]]+v[i]);
}
return f[1][W];
}
優化:由於F[i][j]是從前一個狀態F[i-1][j]得到的,只與前一個狀態有關,那麼即可優化爲一維滾動數組F[],需注意F必須是從右往左計算,因爲F[j]保存的是f[i-1][j]的值,F[j-Wi]保存的是f[i-1][j-Wi]的值,而不是f[i][j-Wi],這樣,F[j]保存的就是max(f[i-1][j],f[i-1][j-Wi])覆蓋原來的f[i-1][j]//注意:便於敘述,這裏F[]表示優化的滾動數組,f[][]是原來的二維數組。代碼如下:
int DP1()
{
memset(f,0,sizeof(f));
for(int i=1;i<=N;i++)
for(int j=W;j>=w[i];j--)
f[j]=max(f[j],f[j-w[i]+v[i])
return f[W];
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
(2)完全揹包
完全揹包是在01揹包的基礎上,物品爲的數量由一個變爲無限個,求滿足重量的條件下最大價值。由上述的01揹包,很容易就能想到通過枚舉每種物品的數量來求最大值,那麼需要再加一層循環來限定第i件物品的數量,此時公式爲:F[i][j]=MAX(F[i-1][j],F[i-1][j-k*w[i]]+k*v[i])1<=k<=W/Wi不過很遺憾,這種算法的複雜度非常高,再仔細考慮一下,會發現若兩件物品i、j滿足v[i]>=v[j]且w[i]<=w[j],則將物品j去掉,不用考慮。即,如果一個物品A是佔的地少且價值高,而物品B是佔地多,但是價值不怎麼高,那麼肯定是優先考慮A物品的。那麼公式即爲:F[i][j]=MAX(F[i-1][j],F[i][j-w[i]]+v[i],代碼如下:
for(int i=1;i<=N;i++)
for(int j=0;j<=W;j++)
{
if(j<w[i])
f[i][j]=f[i-1][j];
else
f[i][j]=max(f[i-1][j],f[i][j-w[i]]+v[i];
}
-------------------------------------------------------------------------------------------------------
(3)多重揹包
多重揹包是每件物品都有給定的數量,如果按照安全揹包的思想,每件物品的數量爲num[i],即1<=k<=num[i],若按三層循環來寫,複雜度也挺高!那麼有一個非常奇妙的二進制思想,任何數都可以用二進制來表示,如果一個數N能用1,2,4,8,16....的和來表示的話,那麼從1到N之間的任意數字都可以用這些數來表示,比如第i種物品的數量爲7,重量爲2,價值爲3,分析:7可分爲1,2,4即該種物品被分成重量爲2*1,2*2,2*4價值爲3*1,3*2,3*4的三種物品,那麼就大大減少了物品的數量,推測以一億個物品按這種方式最多分爲30多個物品,這樣就能用01揹包的知識來解決這個問題,代碼如下:
struct ss
{
int x;//物品重量
int y;//物品價值
int z;//物品數量
}s[1000];//假設1000種物品
int DP1()
{
int t=-1;
for(int i=1;i<=N;i++)
{
t++;
int sum=1;
int sum1=1;
w[t]=s[i].x;
v[t]=s[i].y;
while(sum1<s[i].z)
{
sum*=2;
sum1+=sum;
if(sum1<s[i].z)
{
w[t]=sum*s[i].x;
v[t]=sum*s[i].y;
}
}
if(sum1!=s[i].z)
{
t++;
w[t]=(s[i].z-(sum1-sum))*s[i].x;
v[t]=(s[i].z-(sum1-sum))*s[i].y;
}
}
memset(f,0,sizeof(f));
for(int i=0;i<=t;i++)
for(int j=W;j>=w[i],j--)
f[j]=max(f[j],f[j-w[i]+v[i]);
return f[W];
}