1. 程式人生 > >深度優先搜尋+動態規劃——01揹包類似問題

深度優先搜尋+動態規劃——01揹包類似問題

描述
今天是陰曆七月初五,acm隊員zb的生日。zb正在和C小加、never在武漢集訓。他想給這兩位兄弟買點什麼慶祝生日,經過調查,zb發現C小加和never都很喜歡吃西瓜,而且一吃就是一堆的那種,zb立刻下定決心買了一堆西瓜。當他準備把西瓜送給C小加和never的時候,遇到了一個難題,never和C小加不在一塊住,只能把西瓜分成兩堆給他們,為了對每個人都公平,他想讓兩堆的重量之差最小。每個西瓜的重量已知,你能幫幫他麼?
輸入 多組測試資料(<=1500)。資料以EOF結尾 第一行輸入西瓜數量N (1 ≤ N ≤ 20) 第二行有N個數,W1, …, Wn
(1 ≤ Wi ≤ 10000)分別代表每個西瓜的重量 輸出 輸出分成兩堆後的質量差
樣例輸入
5
5 8 13 27 14
樣例輸出
3

最開始尋思著用貪心的思想來解決,將重量按照順序從大到小排序,把每一件物品放入兩堆中的某一個,使得重量差最小。使用區域性最優,達到全域性最優的效果。然而,貪心法在這裡是不起作用的,會存在錯解。
目前有一個想法,有沒有可能進行數學建模後,運用凸函式理論來證明貪心演算法不正確的情形?先mark一個,有進展再更。
下面給出兩種正確解法。

深度優先搜尋解決

其實就是簡單的暴力列舉法,把所有可能性都找出來,然後求得差值最小的那種情況。
對於列舉,要考慮到的就是每件物品只有兩種狀態,要麼裝入揹包,要麼不裝入揹包。那麼,進行遞迴搜尋時,每一層就只考慮這兩種狀態。詳見程式碼。

#include<stdio.h>
#include<math.h>
int W[22],sum=0,N=0,minmize=10000,temp;
int dfs(int cur,int total)
{
    temp=fabs((sum-total)-total);
    if(temp<minmize)
        minmize=temp;
    if(cur==N || total>sum/2)
        return 0;
    dfs(cur+1,total+W[cur]); //第一種情況,當前物品放入揹包,所以計入總價值 
    dfs(cur+1,total); // 第二種情況,當前物品不放入揹包 
}
int main()
{
    int i;
    while(scanf("%d",&N)!=EOF)
    {
        for(i=0;i<N;i++)
        { 
            scanf("%d",&W[i]);
            sum+=W[i]; 
        } 
        dfs(0,0);
        printf("%d\n",minmize);
    }
    return 0;
} 

動態規劃解決

上邊的題目類似於揹包問題。
原始問題就是將一堆數值分成兩堆,使其每堆數值之和的差值最小化。將以上問題轉化一下,用揹包問題來解決。
問題的實質就是,有一個揹包,其能裝入的最多重量是那所有數值之和的一半,那一堆數值,代表物品的重量,而每一件物品的價值恰好等於其重量,求能裝入揹包的最大價值問題。

對於每一件物品,只有兩種狀態,要麼放入揹包,要麼不放入揹包,其最高價值的遞推方程如下:

圖片

其中,F[i-1][j] 代表的意思是 在揹包重量為 j 時,從前 (i-1)個物品裡選擇物品放入揹包能裝入的最大價值。
那麼,第 i 個物品就有兩種選擇。如果揹包的重量不允許第 i 個物品放入,那麼它的價值就不會變,仍然是 F[ i - 1 ][ j ] ;如果揹包剛好允許第 i 個物品放入,那麼它的價值就應該為 在減掉第 i 個物品重量時揹包的價值的基礎上 再加上第 i 個物品本身的價值,最後就得到了剛好裝入第 i 物品時,揹包的最大價值。
而第 i 件物品到底放不放入揹包,就取決於 是否會增大揹包的價值,也就是取兩種情況的大者。
程式碼如下。

#include<stdio.h>
#include<string.h>
int max(int a,int b)
{
    return a>b?a:b ;
}
int main()
{
    int W[25],sum=0,N=0,weight[100005];
    int i,j,temp;
    memset(weight,0,sizeof(weight));
    while(scanf("%d",&N)!=EOF)
    {
        for(i=0;i<N;i++)
        { 
            scanf("%d",&W[i]);
            sum+=W[i]; 
        }
        temp=sum/2;
        for(i=0;i<N;i++)        //動態規劃演算法 
        {
            for(j=temp;j>=W[i];j--)
            {
                weight[j]=max(weight[j-1],weight[j-W[i]]+W[i]);
            }
        }
        temp=(sum-weight[temp])-weight[temp];
        printf("%d\n",temp);
    }
    return 0;
}