1. 程式人生 > 實用技巧 >動態規劃(三)——如何巧妙解決“雙十一”購物時的湊單問題?

動態規劃(三)——如何巧妙解決“雙十一”購物時的湊單問題?

1 題目描述

  淘寶的“雙十一”購物節有各種促銷活動,比如“滿 200 元減 50 元”。假設你女朋友的購物車中有 n 個(n>100)想買的商品,她希望從裡面選幾個,在湊夠滿減條件的前提下,讓選出來的商品價格總和最大程度地接近滿減條件(200 元),這樣就可以極大限度地“薅羊毛”。作為程式設計師的你,能不能編個程式碼來幫她搞定呢?

2 輸入

  第一行是物品的個數n(1≤n≤100000),滿減大小w(1≤w≤1000000);

  第二行是n個物品的價值。

3 輸出

  輸出最小值

4 樣例輸入

6 200
34, 23, 81, 74, 57, 65

5 樣例輸出

203

6 求解思路

  實際上,它跟第一個例子中講的 0-1 揹包問題很像,只不過是把“重量”換成了“價格”而已。購物車中有 n 個商品。我們針對每個商品都決策是否購買。每次決策之後,對應不同的狀態集合。我們還是用一個二維陣列 states(n)(x),來記錄每次決策之後所有可達的狀態。

  不過,這裡的 x 值是多少呢?0-1 揹包問題中,我們找的是小於等於 w 的最大值,x 就是揹包的最大承載重量 w+1。對於這個問題來說,我們要找的是大於等於 200(滿減條件)的值中最小的,所以就不能設定為 200 加 1 了。就這個實際的問題而言,如果要購買的物品的總價格超過 200 太多,比如 1000,那這個羊毛“薅”得就沒有太大意義了。所以,我們可以限定 x 值為 1001。

  同樣的,這個題目也可以使用回溯法與動態規劃兩種方法解決。

  這裡特別強調一下當使用一個數組時,即程式碼中的第 53 行,j 需要從大到小來處理。如果我們按照 j 從小到大處理的話,會出現 for 迴圈重複計算的問題,而且當揹包限制過大的時候,結果會出錯。

7 動態規劃C++版本程式碼如下

#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;

#define MAXNUM 100010
#define DRIFT 1001

// items商品價格,n商品個數, w表示滿減條件,比如200
int girlLove(int items[], int n, int w) {
    bool states[n][w + 10];
    memset(states, false, sizeof(states));
    // 第一行的資料要特殊處理
    states[0][0] = true;
    if (items[0] <= w + 10) {
        states[0][items[0]] = true;
    }
    for (int i = 1; i < n; ++i) { // 動態規劃
        // 不購買第i個商品
        for (int j = 0; j <= w + 10; ++j)
            if (states[i-1][j])
                states[i][j] = states[i-1][j];
        for (int j = 0; j <= w + 10-items[i]; ++j)
            //購買第i個商品
            if (states[i-1][j]){
                //cout<<j+items[i]<<endl;
                states[i][j+items[i]] = true;
            }
    }
    for(int i = 0; i <= w; i++)
        cout<<states[n - 1][i]<<" ";
    cout<<endl;
    int j;
    for (j = w; j < w + 10; ++j) {
        // 輸出結果大於等於w的最小值
        if (states[n-1][j] == true)
            return j;
    }
    // 沒有可行解
    if (j == w + 10 +1)
        return -1;
}

int girlLovePlus(int items[], int n, int w) {
    bool states[w + 10];
    memset(states, false, sizeof(states));
    // 第一行的資料要特殊處理
    states[0] = true;
    if (items[0] <= 3 * w)
        states[items[0]] = true;

    for (int i = 1; i < n; ++i) {
        for(int j = w + 10 - items[i]; j >= 0; j--)
            if(states[j])
                states[j + items[i]] = true;
    }
    for(int i = 0; i <= w; i++)
        cout<<states[i]<<" ";
    cout<<endl;
    int j;
    for (j = w; j < w + 10; ++j) {
        // 輸出結果大於等於w的最小值
        if (states[j] == true)
            return j;
    }
    // 沒有可行解
    if (j == w + 10 + 1)
        return -1;
}

int main()
{
    int value[6] = {34, 23, 81, 74, 57, 65};
    cout<<girlLove(value, 6, 200);
    cout<<endl;
    cout<<girlLovePlus(value, 6, 200);
    return 0;
}