動態規劃擴展:揹包小結

01揹包是基礎的揹包問題,即容量爲v的揹包, 給你n件物品, 每件物品只有一件, 每件物品所佔體積vi, 價值wi已知,求此揹包所能容納的前提下,讓在其中物品價值最大。

此問題狀態方程爲發f[i][v] = max(f[i-1][v], f[i-1][v-vi[i]]+wi[i]), 即前i件物品放入容積爲v的揹包中得到的最大價值。

因爲每件物品只有一件,所以第i件物品有兩種選擇, 即放或不放。

max(f[i-1][v], f[i-1][v-vi[i]]+wi[i])中,f[i-1][v]即選擇不放, 價值保持放入i-1 個物品時的價值;
而f[i-1][v-vi[i]]+wi[i]即選擇將第i件物品放入, 從其中選擇較大的值就是即爲前i件物品放入容積爲v的揹包得到的最大價值,最後f[n][v]就是要求的價值最大值;

code:
for(i = 0;i<n;i++)
        {
            for(j = vi[i];j<=v;j++)
            {
                f[i][j] = max(f[i-1][j], f[i-1][j-vi[i]]+wi[i]);
            }
        }

另外, 還可用一維數組代替二維數組的方法實現空間優化,即用最新的一組數據代替原先的上一組數據,不過此方法寫循環式需要逆序循環, 即二層循環從v開始一直到vi[i],這樣就保證了每個物品只拿一次,而順序則不能保證,可能拿多次。
於是f[v] = max(f[v], f[v-vi]+wi[i], 最後的f[v]即爲要求的最大價值。

code:
for(i = 0;i<n;i++)
        {
            for(j = v;j>=vi[i];j--)
            {
                f[j] = max(f[j], f[j-vi[i]]+wi[i]);
            }
        }

例題1:
Problem Description

多組輸入。

對於每組輸入,第一行有兩個整數N,X(1 < = N < = 100,1 < = X < = 1000),分別表示哈士奇的數量和高數巨的錢數

對於每組數據,輸出一個整數,表示高數巨最多可以獲得的萌值,每組輸出佔一行

Example Input

2 100
50 20
60 40
3 100
20 55
20 35
90 95
1 10
20 50
Example Output

40
95
0
明顯的01揹包問題,這裏所帶的錢就相當於揹包的體積,萌值就是價值,每條二哈的價錢可看做每個物品所佔的體積。

code:
#include<stdio.h>
#include<string.h>
int max(int a, int b)
{
    if(a>b) return a;
    else return b;
}
int main()
{
    int b[1200];
    int n, x, pi[1200], mi[1200], i, j, a;
    while(~scanf("%d%d", &n, &x))
    {
        memset(b, 0, sizeof(b));
        for(i = 0;i<n;i++)
        {
            scanf("%d%d", &pi[i], &mi[i]);
        }
        for(i = 0;i<n;i++)
        {
            for(j = x;j>=pi[i];j--)
            {
                b[j] = max(b[j], b[j-pi[i]]+mi[i]);
            }
        }
        printf("%d\n", b[v]);
    }
}
有了01揹包基礎,接下來的完全揹包聚會簡單很多,完全揹包實際上就是將每種物品的數量從一件變爲無限件。那麼設某i件物品拿k件,那麼狀態方程爲

f[i][v]=max(f[i-1][v-k*v[i]]+k*w[i]|0<=k*v[i]<=v),這樣需要三層循環,找出k爲何值時使得f得到最大值;

另外完全揹包也可以使用一維數組進行空間優化, 可以用01揹包提到過的正序循環,這樣就可以保證每件物品拿多次。

01揹包中要按照v=V..0的逆序來循環。這是因爲要保證第i次循環中的狀態f[i][v]是由狀態f[i-1] [v-c[i]]遞推而來。換句話說,這正是爲了保證每件物品只選一次,保證在考慮“選入第i件物品”這件策略時,依據的是一個絕無已經選入第i件物品的 子結果f[i-1][v-v[i]]。而現在完全揹包的特點恰是每種物品可選無限件,所以在考慮“加選一件第i種物品”這種策略時,卻正需要一個可能已選入第i種物品的子結果f[i][v-v[i]],所以就可以並且必須採用v=0..V的順序循環。(本段節錄自揹包九講)
code:
for(i = 0;i<n;i++)
        {
            for(j = vi[i];j<=v;j++)
            {
                f[j] = max(f[j], f[j-vi[i]]+wi[i]);
            }
        }

例題1:

Problem Description

話說,上次小P到伊利哇呀國旅行得到了一批寶藏。他是相當開心啊,回來就告訴了他的好基友小鑫,於是他們又結伴去伊利哇呀國尋寶。
這次小P的尋寶之路可沒有那麼的輕鬆,他們走到了一個森林,小鑫一不小心被觸發了機關,被困在了一個大籠子裏面,籠子旁邊上有一道題目和一個密碼鎖,上面說只要解出此題輸入密碼即可救出被困人。小鑫不是很聰明,所以他做不出來,他知道小P很笨,更解不出來。所以他就讓小P獨自回去,不用管他。但是小P重情重義不會拋棄他離去。他說:“不,好基友一起走!”。於是就感動了上帝,上帝特派你來替他們解決問題。聰明的你要加油了啊!
題目描述:給你n種物品和一個體積爲v的包包。每種物品有無數種,體積是vi價值是wi。求出包包v所能裝的最大價值的東西。
Input

多組輸入。第一行有兩個正整數n(0<n<=10000), v(0<v<= 10000)。接下來兩行每行有n個數字。第一行表示每種物品的價值wi(0<wi<100),第二行表示每種物品的體積vi(0<vi<100)。
Output

輸出最多可以得到的價值。輸出結果救出小鑫。
Example Input

5 20
1 2 3 4 5
2 6 3 5 4
Example Output

25
code:

#include<stdio.h>
#include<string.h>
int max(int a, int b)
{
    if(a>b) return a;
    else return b;
}
int main()
{
    int n, v, vi[10010], wi[10010], a[10010];
    int i, j;
    while(~scanf("%d%d", &n, &v))
    {
        memset(a, 0, sizeof(a));
        for(i = 0;i<n;i++)
        {
            scanf("%d", &wi[i]);
        }
        for(i = 0;i<n;i++)
        {
            scanf("%d", &vi[i]);
        }
        for(i = 0;i<n;i++)
        {
            for(j = vi[i];j<=v;j++)
            {
                a[j] = max(a[j], a[j-vi[i]]+wi[i]);
            }
        }
        printf("%d\n", a[v]);
    }
}

————————————————————————————————————————————————————————

小P的故事——神奇的Dota

Time Limit: 1000 ms Memory Limit: 65536 KiB

Submit Statistic

Problem Description

小P非常喜歡玩dota,不分晝夜的玩

,結果他連做夢也都是裏面的畫面,一天晚上小P剛躺下就做了一個神奇的夢。。。

不死族的巫妖王發工資拉,死亡騎士拿到一張N元的鈔票(記住,只有一張鈔票),爲了防止自己在戰鬥中頻繁的死掉,他決定給自己買一些道具,於是他來到了地精商店前.

死亡騎士:"我要買道具!"

地精商人:"我們這裏有三種道具,血瓶150塊一個,魔法藥200塊一個,無敵藥水350塊一個."

死亡騎士:"好的,給我一個血瓶."

說完他掏出那張N元的大鈔遞給地精商人.

地精商人:"我忘了提醒你了,我們這裏沒有找客人錢的習慣的,多的錢我們都當小費收了的,嘿嘿."

死亡騎士:"......"

死亡騎士想,與其把錢當小費送個他還不如自己多買一點道具,反正以後都要買的,早點買了放在家裏也好,但是要儘量少讓他賺小費.

現在死亡騎士感覺自己的智商不夠用所以希望小P幫他計算一下,最少他要給地精商人多少小費.但是小P的智商可是出了名的“不忍直視”啊,聰明非凡的你所以你能幫幫他嗎?

Input

輸入數據的第一行是一個整數T(1<=T<=100),代表測試數據的數量.然後是T行測試數據,每個測試數據只包含一個正整數N(1<=N<=10000),N代表死亡騎士手中鈔票的面值.

 

注意:地精商店只有題中描述的三種道具.

Output

 對於每組測試數據,請你輸出死亡騎士最少要浪費多少錢給地精商人作爲小費.

 

Sample Input

2 
380 
200

Sample Output

30 
0

Hint

 

Source

xfl

這也是一個可以那多次的揹包問題,所以內循環要用正序;

#include<bits/stdc++.h>
using namespace std;

int main()
{

    int dp[10010],w[3]={150,200,350};
    int i,j,n,t;
    cin>>t;
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        cin>>n;
        for(i=0;i<=2;i++)
        {
            for(j=w[i];j<=n;j++)
            {
                dp[j]=max(dp[j],dp[j-w[i]]+w[i]);
            }
        }
        cout<<n-dp[n]<<endl;

    }
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章