oj算法----動態規劃----揹包問題

oj算法----動態規劃----揹包問題

1.動態規劃

1.1概念html

動態規劃(dynamic programming)是運籌學的一個分支,是求解決策過程(decision process)最優化的數學方法ios

1.2性質算法

動態規劃通常用來處理最優解的問題。使用動態規劃算法思想解決的問題通常具備最優子結構性質和重疊子問題這兩個因素。數組

<1> 最優子結構ide

     一個問題的最優解包含其子問題的最優解,這個性質被稱爲最優子結構性質 學習

<2> 重疊子問題優化

     遞歸算法求解問題時,每次產生的子問題並不老是新問題,有些子問題被反覆計算屢次。這種性質稱爲子問題的重疊性質。spa

1.3與分治法的區別.net

動態規劃和分治法有類似之處,都是將待解決問題分解爲若干子問題。不一樣之處,分治法求解時有些子問題被重複計算了許屢次;動態規劃實現了存儲這些子問題的解,以備子問題重複出現,當重疊子問題出現,找到已解決子問題的解便可,避免了大量的重複計算。code

1.4在優化

能夠考慮在空間上進行優化,例如揹包問題,將二維數組優化成兩個一維數組,進而再優化成一個一維數組

2.揹包問題

2.1揹包問題又分紅0/1揹包問題,徹底揹包問題,以及多重揹包問題(本質區別就是到底max多少種狀況)

2.2 01揹包問題

遞推關係式

① j<w(i)      V(i,j)=V(i-1,j) 

② j>=w(i)     V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }

 圖解

必定要注意初始化的問題

 

 空間優化

由上面的圖能夠看出來,每一次V(i)(j)改變的值只與V(i-1)(x) {x:1...j}有關,V(i-1)(x)是前一次i循環保存下來的值;

所以,能夠將V縮減成一維數組,從而達到優化空間的目的,狀態轉移方程轉換爲 B(j)= max{B(j), B(j-w(i))+v(i)}

而且,狀態轉移方程,每一次推導V(i)(j)是經過V(i-1)(j-w(i))來推導的,因此一維數組中j的掃描順序應該從大到小(capacity到0),否者前一次循環保存下來的值將會被修改,從而形成錯誤。

#include <stdio.h>
#include <string.h>
 
int f[1010],w[1010],v[1010];//f記錄不一樣承重量揹包的總價值,w記錄不一樣物品的重量,v記錄不一樣物品的價值
 
int max(int x,int y){//返回x,y的最大值
    if(x>y) return x;
    return y;
}
 
int main(){
    int t,m,i,j;
    memset(f,0,sizeof(f));  //總價值初始化爲0
    scanf("%d %d",&t,&m);  //輸入揹包承重量t、物品的數目m
    for(i=1;i<=m;i++)
        scanf("%d %d",&w[i],&v[i]);  //輸入m組物品的重量w[i]和價值v[i]
    for(i=1;i<=m;i++){  //嘗試放置每個物品
        for(j=t;j>=w[i];j--){//倒敘是爲了保證每一個物品都使用一次
            f[j]=max(f[j-w[i]]+v[i],f[j]);
            //在放入第i個物品先後,檢驗不一樣j承重量揹包的總價值,若是放入第i個物品後比放入前的價值提升了,則修改j承重量揹包的價值,不然不變
        }
    }
    printf("%d",f[t]);  //輸出承重量爲t的揹包的總價值
    printf("\n");
    getch();
    return 0;
} 
01揹包空間優化後

2.3徹底揹包

每種物品的數量不限制

2.3.1 簡單方法

根據第i種物品放多少件進行決策,因此狀態轉移方程爲

   

與01揹包相同,徹底揹包也須要求出NV個狀態F[i][j]。可是徹底揹包求F[i][j]時須要對k分別取0,…,j/C[i]求最大F[i][j]值。(實際上就是更新每一個狀態的時候再也不是max兩種狀況,放入一個或者放入0個,而是maxK種狀況,放入0個,1個,2個,3個......直到k個

這樣代碼應該是三層循環(物品數量,物品種類,揹包大小這三個循環)

F[0][] ← {0}  
  
F[][0] ← {0}  
  
for i←1 to N  
  
    do for j←1 to V  
  
        do for k←0 to j/C[i]  
  
           if(j >= k*C[i])  
  
                then F[i][k] ← max(F[i][k],F[i-1][j-k*C[i]]+k*W[i])  
  
return F[N][V]
徹底揹包簡單方法僞代碼

這樣是三層循環,因此考慮優化

2.3.2 優化方法

 

這裏注意,當j>=C[i]時候,是對照F[i][j-C[j]]更新,而不是F[i-1][j-C[j]]。由於F[i][j-C[j]]狀態自己包含了max了k-1種狀況(他max了放入0個,放入1個......放入k-1的狀況),然後者只max了一種狀況,就是放入0個的狀況!!!

2.3.3 0-1揹包和徹底揹包的不一樣:

從二維數組上區別0-1揹包和徹底揹包也就是狀態轉移方程就差異在放第i中物品時,徹底揹包在選擇放這個物品時,最優解是F[i][j-c[i]]+w[i]即畫表格中同行的那一個,而0-1揹包比較的是F[i-1][j-c[i]]+w[i],上一行的那一個。

從一維數組上區別0-1揹包和徹底揹包差異就在循環順序上,0-1揹包必須逆序,由於這樣保證了不會重複選擇已經選擇的物品,而徹底揹包是順序,順序會覆蓋之前的狀態,因此存在選擇屢次的狀況,也符合徹底揹包的題意。狀態轉移方程都爲F[i] = max(F[i],dp[F-c[i]]+v[i])。
2.3.4 代碼實現徹底揹包

#include<cstdio>
#include<algorithm>
using namespace std;
int w[300],c[300],f[300010];
int V,n;
int main()
{
    scanf("%d%d",&V,&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d%d",&w[i],&c[i]);
    }
    for(int i=1; i<=n; i++)
        for(int j=w[i]; j<=V; j++)//注意此處,與0-1揹包不一樣,這裏爲順序,0-1揹包爲逆序
            f[j]=max(f[j],f[j-w[i]]+c[i]);
    printf("max=%d\n",f[V]);
    return 0;
}
徹底揹包一維數組優化

2.4 多重揹包

2.4.1 簡單方法

就是max有限個m種狀況

2.4.2 優化方法

轉化爲01揹包求解:把第i種物品換成n[i]件01揹包中的物品。考慮二進制的思想,考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0..n[i]件——均能等價於取若干件代換之後的物品。另外,取超過n[i]件的策略必不能出現。

方法是:將第i種物品分紅若干件物品,其中每件物品有一個係數,這件物品的費用和價值均是原來的費用和價值乘以這個係數。使這些係數分別爲1,2,4,...,2^(k-1),n[i]-2^k+1,且k是知足n[i]-2^k+1>0的最大整數。例如,若是n[i]爲13,就將這種物品分紅係數分別爲1,2,4,6的四件物品。

分紅的這幾件物品的係數和爲n[i],代表不可能取多於n[i]件的第i種物品。另外這種方法也能保證對於0..n[i]間的每個整數,都可以用若干個係數的和表示,證實我也沒看過這裏就不貼上了,主要仍是須要去理解代碼,代碼在下面給出。
2.4.3 代碼實現

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
#define MAX 1000000
using namespace std;
 
int dp[MAX];//存儲最後揹包最大能存多少
int value[MAX],weight[MAX],number[MAX];//分別存的是物品的價值,每個的重量以及數量
int bag;
 
void ZeroOnePack(int weight,int value )//01揹包
{
    int i;
    for(i = bag; i>=weight; i--)
    {
        dp[i] = max(dp[i],dp[i-weight]+value);
    }
}
void CompletePack(int weight,int value)//徹底揹包
{
    int i;
    for(i = weight; i<=bag; i++)
    {
        dp[i] = max(dp[i],dp[i-weight]+value);
    }
}
void MultiplePack(int weight,int value,int number)//多重揹包
{
    if(bag<=number*weight)//若是總容量比這個物品的容量要小,那麼這個物品能夠直到取完,至關於徹底揹包
    {
        CompletePack(weight,value);
        return ;
    }
    else//不然就將多重揹包轉化爲01揹包
    {
        int k = 1;
        while(k<=number)
        {
            ZeroOnePack(k*weight,k*value);
            number = number-k;
            k = 2*k;//這裏採用二進制思想
        }
        ZeroOnePack(number*weight,number*value);
    }
}

int main()
{
    int n;
    while(~scanf("%d%d",&bag,&n))
    {
        int i,sum=0;
        for(i = 0; i<n; i++)
        {
            scanf("%d",&number[i]);//輸入數量
            scanf("%d",&value[i]);//輸入價值  此題沒有物品的重量,能夠理解爲體積和價值相等
        }
        memset(dp,0,sizeof(dp));
        for(i = 0; i<n; i++)
        {
            MultiplePack(value[i],value[i],number[i]);//調用多重揹包,注意穿參的時候分別是重量,價值和數量
        }
        cout<<dp[bag]<<endl;
    }
    return 0;
}
 
多重揹包

其餘

參考連接

https://blog.csdn.net/niaonao/article/details/78249256

https://blog.csdn.net/qq_38984851/article/details/81133840

https://www.cnblogs.com/aiguona/p/7274222.html

拓展

1.硬幣問題本質和揹包問題同樣,只不過有一種無解的狀態

2.學習二進制壓縮的思想,其思想十分的優雅和先進

相關文章
相關標籤/搜索