ACM算法筆記(三)背包問(wèn)題_01背包

首發(fā)csdn,鏈接:https://blog.csdn.net/Colicsin/article/details/115403831

問(wèn)題描述:

*現(xiàn)在給你一個(gè)容量為V的背包,有N個(gè)物品婿失,其中第i件物品的重量為wi纫版,價(jià)值為vi汇鞭,每件物品只可以拿一次耘沼,問(wèn)在有限的容量?jī)?nèi),最多可以拿到多少價(jià)值的物品盅藻。 *

問(wèn)題分析:

對(duì)于每一個(gè)物品购桑,都有兩種策略:拿或不拿。

讀到這里氏淑,是不是腦海中有一個(gè)清晰的想法勃蜘?DFS!確實(shí)假残,這不就是我們常見的dfs問(wèn)題嗎缭贡,分別枚舉拿和不拿兩個(gè)狀態(tài)即可。于是寫下了如此代碼...

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=1e6+7;
int w[MAXN],N,V,v[MAXN],book[MAXN],end=0;
void dfs(int now,int ans,int rest_v)
{
    if(now>N)
    {
        end=max(ans,end);
        return;
    }
    if(book[now]==false&&rest_v>=w[now])
    {
        ans+=v[now];
        rest_v-=w[now];
        book[now]=true;
        dfs(now+1,ans,rest_v);
        ans-=v[now];
        rest_v+=w[now];
        book[now]=false;
    }
    dfs(now+1,ans,rest_v);
}
int main()
{
    cin>>V>>N;
    for(int i=1;i<=N;i++)
    {
        cin>>w[i]>>v[i];
    }
    dfs(1,0,V);
    cout<<end<<endl;
    return 0;
}//標(biāo)準(zhǔn)的暴力dfs模板守问,遺憾30分qwq

很遺憾對(duì)于洛谷P1048的01背包模板題來(lái)說(shuō)匀归,只有30分坑资,剩下70分全部都是TLE(這是什么人間疾苦:呐痢)

點(diǎn)此訪問(wèn)洛谷P1048

不過(guò)轉(zhuǎn)念一想好像確實(shí),我們平常dfs模板的代碼不就是解決小數(shù)據(jù)問(wèn)題的么袱贮,對(duì)于中等數(shù)據(jù)量和大數(shù)據(jù)問(wèn)題仿便,不會(huì)吧不會(huì)吧不會(huì)吧,不會(huì)真的有人想寫dfs吧?(老陰陽(yáng)怪氣了)

現(xiàn)在我們手動(dòng)模擬一下...

很顯然嗽仪,當(dāng)n特別大的時(shí)候(我覺(jué)得到100之后就已經(jīng)夠嗆了)我們的程序變得特別慢荒勇,難免TLE(原來(lái)是自己老了哈哈哈哈)

不過(guò),車到山前必有路闻坚,我們觀察這棵橫著的遞歸樹沽翔,發(fā)現(xiàn)是不是有很多重復(fù)遍歷的部分?

比如選3窿凤,選4這個(gè)小方案仅偎,在選1,選2雳殊、選1不選2橘沥、不選1選2、不選1不選2的時(shí)候都需要訪問(wèn)一次夯秃,現(xiàn)在還只是有4個(gè)物品的情況座咆,要是有n個(gè)物品,那豈不是2^n的數(shù)量級(jí)了仓洼?實(shí)在可怕介陶,這樣一想,我們之前的代碼TLE的關(guān)鍵原因也出來(lái)了色建,就是重復(fù)遍歷的太多了斤蔓。

這時(shí)候我們就需要一個(gè)maxn_數(shù)組來(lái)輔助剪枝一下,maxn_[i][j]表示前i個(gè)物品還剩j點(diǎn)空間下能存放的物品價(jià)值的最大值镀岛,設(shè)想我們現(xiàn)在從不選1選2走到了選3選4這條路上弦牡,但是發(fā)現(xiàn)還沒(méi)走之前所選物品的值已經(jīng)比其他的路到達(dá)選3選4的路時(shí)所選的物品的值小了,那這條路我們還有必要去走嘛漂羊?Ofcourse not驾锰!所以就直接return就好咯,這樣子就完成了剪枝任務(wù)bingo走越!

如果折現(xiàn)回到代碼上椭豫,是這樣子...

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=1e6+7;
int w[MAXN],N,V,v[MAXN],book[MAXN],end=0,maxn_[1001][1001];
void dfs(int now,int ans,int rest_v)
{
    if(ans<=maxn_[now][rest_v])
        return;
    maxn_[now][rest_v]=ans;
    if(now>N)
    {
        end=max(ans,end);
        return;
    }
    if(book[now]==false&&rest_v>=w[now])
    {
        ans+=v[now];
        rest_v-=w[now];
        book[now]=true;
        dfs(now+1,ans,rest_v);
        ans-=v[now];
        rest_v+=w[now];
        book[now]=false;
    }
    dfs(now+1,ans,rest_v);
}
int main()
{
    cin>>V>>N;
    memset(maxn_,-1,sizeof(maxn_));
    for(int i=1;i<=N;i++)
    {
        cin>>w[i]>>v[i];
    }
    dfs(1,0,V);
    cout<<end<<endl;
    return 0;
}//記憶化搜索AC代碼

不過(guò)比起遞歸,01背包最好的方式還是遞推旨指,這也是為什么01背包能當(dāng)作動(dòng)態(tài)規(guī)劃初步引入例題的原因了赏酥。

我們假設(shè)dp[i][j]表示前i件物品用j的容量去裝,所能達(dá)到的最大價(jià)值谆构。(是不是很熟悉裸扶?沒(méi)錯(cuò),就是剛才記憶化搜索用到的那個(gè)判斷繼續(xù)不繼續(xù)的標(biāo)準(zhǔn))

對(duì)于每一次決策搬素,有兩個(gè)狀態(tài):選或不選

對(duì)于每一個(gè)狀態(tài)的值呵晨,都依賴于上一個(gè)狀態(tài)的值

滿足動(dòng)態(tài)規(guī)劃的條件√

很容易寫出狀態(tài)轉(zhuǎn)移方程:

dp[i][j]=min(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);

說(shuō)得更清楚一些魏保,dp[i-1][j]不就代表這一件不選,那么就相當(dāng)于前i-1件物品放入j容量的背包的最大價(jià)值摸屠,dp[i-1][j-w[i]]代表這一件要了谓罗,前i-1件物品放入j-w[i]的容量?jī)?nèi)的最大價(jià)值。

因此我們可以寫出以下代碼....

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN=1e3+7;
int w[MAXN],N,V,v[MAXN],dp[MAXN][MAXN];
int main()
{
    memset(dp,0,sizeof(dp));
    cin>>V>>N;
    for(int i=1;i<=N;i++)
    {
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=N;i++)
    {
        for(int j=V;j>=0;j--)
        {
            if(j>=w[i])
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
            else
                dp[i][j]=dp[i-1][j];
        }
    }
    cout<<dp[N][V]<<endl;
    return 0;
}//二維數(shù)組AC代碼

但是事情就到這里就結(jié)束了嗎季二?當(dāng)然沒(méi)有檩咱!

沒(méi)錯(cuò),還可以接著優(yōu)化胯舷!時(shí)間優(yōu)化税手?并不,空間優(yōu)化需纳。畢竟二維數(shù)組還是很坑的芦倒,萬(wàn)一比賽碰到了一些“善良的死神”,直接MLE了....

從上面我們分析注意到不翩,每一層i的狀態(tài)都和上一層i-1的狀態(tài)有關(guān)兵扬,這樣子我們是不是并不需要開二維數(shù)組呢?這個(gè)時(shí)候口蝠,滾動(dòng)數(shù)組閃亮出場(chǎng)~

啥是滾動(dòng)數(shù)組器钟?這還要從頭憶起,說(shuō)簡(jiǎn)單點(diǎn)妙蔗,滾動(dòng)數(shù)組就是一個(gè)很小的數(shù)組傲霸,一直滾...一直滾...好吧其實(shí)就是不斷更新的數(shù)組。

這里用斐波那契數(shù)列來(lái)舉例子確實(shí)再好不過(guò)眉反,因?yàn)殪巢瞧鯏?shù)列第i項(xiàng)就是前兩項(xiàng)之和昙啄,所以對(duì)于斐波那契數(shù)列來(lái)說(shuō),有以下代碼...

f[1]=1;
f[2]=1;
for(int i=2;i<=n;i++)
{
    f[0]=f[1];
    f[1]=f[2];
    f[2]=f[0]+f[1];
}

這樣子最后f[n]就是第n項(xiàng)的值咯~

從滾動(dòng)數(shù)組得到經(jīng)驗(yàn)寸五,我們用滾動(dòng)數(shù)組來(lái)優(yōu)化我們的二維dp代碼...

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN=1e3+7;
int w[MAXN],N,V,v[MAXN],dp[100001];
int main()
{
    memset(dp,0,sizeof(dp));
    cin>>V>>N;
    for(int i=1;i<=N;i++)
    {
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=N;i++)
    {
        for(int j=V;j>=0;j--)
        {
            if(j>=w[i])
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    cout<<dp[V]<<endl;
    return 0;
}//一維數(shù)組AC代碼

Ps:對(duì)于一維數(shù)組梳凛,j循環(huán)必須要倒序遍歷,為了防止多重迭代現(xiàn)象梳杏;

舉個(gè)小例子韧拒;

假設(shè)給定數(shù)據(jù):

10 3

5 5

8 7

4 6

剛開始數(shù)組dp內(nèi)都是初始化為0,因?yàn)闆](méi)有裝入任何東西

第一遍遍歷十性,從前往后

第二遍遍歷叛溢,從前往后

第三次遍歷,從前往后


很顯然劲适,出現(xiàn)了什么情況呢楷掉?居然最大到了17,這里我們可以看到减响,根據(jù)狀態(tài)轉(zhuǎn)移方程式dp[j]=max(dp[j],dp[j-w[i]]+v[i])來(lái)看靖诗,在第三次遍歷(i=3),還未遍歷時(shí)dp[8]=7支示。開始遍歷之后刊橘,dp[8]=max(dp[8],dp[4]+v[3]),這個(gè)時(shí)候發(fā)現(xiàn)肯定是dp[4]+v[3]更大一些颂鸿,但是這個(gè)時(shí)候要注意促绵,dp[4]是已經(jīng)拿完了第三件物品的,按理來(lái)說(shuō)嘴纺,我們并沒(méi)有辦法再去拿第三件物品了败晴,因?yàn)槊恳粋€(gè)物品只能拿一件,所以就行不通咯栽渴,自然就不可以用正序的方法尖坤。而倒序的方法則是先盡可能地都拿著,在最大化的前提下去判斷闲擦,自然是可以的慢味。

01背包問(wèn)題就到此完結(jié)

完結(jié)撒花,感謝陪伴~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末墅冷,一起剝皮案震驚了整個(gè)濱河市纯路,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寞忿,老刑警劉巖驰唬,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異腔彰,居然都是意外死亡叫编,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門霹抛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宵溅,“玉大人,你說(shuō)我怎么就攤上這事上炎∈崖撸” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵藕施,是天一觀的道長(zhǎng)寇损。 經(jīng)常有香客問(wèn)我,道長(zhǎng)裳食,這世上最難降的妖魔是什么矛市? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮诲祸,結(jié)果婚禮上浊吏,老公的妹妹穿的比我還像新娘而昨。我一直安慰自己,他們只是感情好找田,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布歌憨。 她就那樣靜靜地躺著,像睡著了一般墩衙。 火紅的嫁衣襯著肌膚如雪务嫡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天漆改,我揣著相機(jī)與錄音心铃,去河邊找鬼。 笑死挫剑,一個(gè)胖子當(dāng)著我的面吹牛去扣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播樊破,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼厅篓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了捶码?” 一聲冷哼從身側(cè)響起羽氮,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惫恼,沒(méi)想到半個(gè)月后档押,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祈纯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年令宿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腕窥。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粒没,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出簇爆,到底是詐尸還是另有隱情癞松,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布入蛆,位于F島的核電站响蓉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏哨毁。R本人自食惡果不足惜枫甲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧想幻,春花似錦粱栖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至抄沮,卻和暖如春跋核,著一層夾襖步出監(jiān)牢的瞬間岖瑰,已是汗流浹背叛买。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹋订,地道東北人率挣。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像露戒,于是被迫代替她去往敵國(guó)和親椒功。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容