學習日記—線性DP之揹包問題
線性DP學習筆記:
一、什麼是線性DP
線性DP是動態規劃問題中的一類問題,指狀態之間有線性關係的動態規劃問題。(可以理解為DP中最簡單的一種)
二、典型例題
說到DP即使沒學過也應該略有耳聞,這其中似乎有一類題跟DP就好像繫結在了一起,那就是揹包問題。——作者(2022年5月12日)[手動狗頭]
1.P1048[NOIP2005普及組]採藥(01揹包)洛谷傳送門
題目描述
辰辰是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裡對他說:“孩子,這個山洞裡有一些不同的草藥,採每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間裡,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。”如果你是辰辰,你能完成這個任務嗎?
輸入格式
第一行有 22 個整數T(1≤T≤1000)和M(1≤M≤100),用一個空格隔開,T代表總共能夠用來採藥的時間,M代表山洞裡的草藥的數目。接下來的M行每行包括兩個在1到100之間(包括1和100的整數,分別表示採摘某株草藥的時間和這株草藥的價值。
輸出格式
輸出在規定的時間內可以採到的草藥的最大總價值。
輸入輸出樣例
輸入 70 3
71 100
69 1
1 2
輸出
3
分析:DP題寫起來不難,但公式難推,我們觀察,每種草藥只有一株,而且每一株草藥只有採和不採兩種狀態,所以我們就可以寫爆搜了相信大家都已經迫不及待想去寫了,但是,大家看一下它的空間複雜度O(T*M)這複雜度不算大,但duck不必怎麼浪費空間,我們可以優化成一個一維陣列,就像斐波那契數列可以優化成3個變數一樣,優化後的狀態轉移方程長這樣: dp[j]=max(dp[j],dp[j-w[i]]+v[i]) 時間複雜度沒變但空間複雜度變成了O(M),然後我們倒推一下就行了,注意為什麼是倒推原因就是如果正推會出現一種草藥採了好幾遍的情況,那就不符合題意了,題目說的是一種草藥只有一株,也就是隻能採一遍,話不多說上程式碼(!!嚴禁抄襲,抄襲可恥!!)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 int t,n,dp[1005],w[1005],v[1005]; 5 int main() { 6 cin>>t>>n; 7 for(int i=1;i<=n;i++)cin>>w[i]>>v[i]; 8 for(int i=1;i<=n;i++){ 9 for(int j=t;j>=w[i];j--)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//狀態轉移方程 10 } 11 cout<<dp[t]; 12 return 0; 13 }
2.P1616瘋狂的採藥(完全揹包)洛谷傳送門
題目跟上一題採藥很像,但不一樣的是這次每種草藥不止一株而是有無數株,無限採所以是瘋狂採藥,言歸正傳這題聽完我的描述是不是覺得很眼熟,這不就是上一題我們強調倒推是所講的正推嗎,所以上程式碼(!!嚴禁抄襲,抄襲可恥!!)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 ll t,n,dp[10000005],w[10005],v[10005]; 5 int main() { 6 scanf("%ld%ld",&t,&n); 7 for(int i=1;i<=n;i++)scanf("%ld%ld",&w[i],&v[i]); 8 for(int i=1;i<=n;i++){ 9 for(int j=w[i];j<=t;j++)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//只有這的for不一樣 10 } 11 printf("%lld",dp[t]); 12 return 0; 13 }
最後說一句記得看資料範圍不開long long見祖宗
有錯誤歡迎指出,寫博不易點個讚唄
一、什麼是線性DP
線性DP是動態規劃問題中的一類問題,指狀態之間有線性關係的動態規劃問題。(可以理解為DP中最簡單的一種)
二、典型例題
說到DP即使沒學過也應該略有耳聞,這其中似乎有一類題跟DP就好像繫結在了一起,那就是揹包問題。——作者(2022年5月12日)[手動狗頭]
1.P1048[NOIP2005普及組]採藥(01揹包)洛谷傳送門
題目描述
辰辰是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裡對他說:“孩子,這個山洞裡有一些不同的草藥,採每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間裡,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。”如果你是辰辰,你能完成這個任務嗎?
輸入格式
第一行有 22 個整數T(1≤T≤1000)和M(1≤M≤100),用一個空格隔開,T代表總共能夠用來採藥的時間,M代表山洞裡的草藥的數目。接下來的M行每行包括兩個在1到100之間(包括1和100的整數,分別表示採摘某株草藥的時間和這株草藥的價值。
輸出格式
輸出在規定的時間內可以採到的草藥的最大總價值。
輸入輸出樣例
輸入 70 3
71 100
69 1
1 2
輸出
3
分析:DP題寫起來不難,但公式難推,我們觀察,每種草藥只有一株,而且每一株草藥只有採和不採兩種狀態,所以我們就可以寫爆搜了得出以下狀態轉移方程:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])相信大家都已經迫不及待想去寫了,但是,大家看一下它的空間複雜度O(T*M)這複雜度不算大,但duck不必怎麼浪費空間,我們可以優化成一個一維陣列,就像斐波那契數列可以優化成3個變數一樣,優化後的狀態轉移方程長這樣: dp[j]=max(dp[j],dp[j-w[i]]+v[i]) 時間複雜度沒變但空間複雜度變成了O(M),然後我們倒推一下就行了,注意為什麼是倒推原因就是如果正推會出現一種草藥採了好幾遍的情況,那就不符合題意了,題目說的是一種草藥只有一株,也就是隻能採一遍,話不多說上程式碼(!!嚴禁抄襲,抄襲可恥!!)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 int t,n,dp[1005],w[1005],v[1005]; 5 int main() { 6 cin>>t>>n; 7 for(int i=1;i<=n;i++)cin>>w[i]>>v[i]; 8 for(int i=1;i<=n;i++){ 9 for(int j=t;j>=w[i];j--)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//狀態轉移方程 10 } 11 cout<<dp[t]; 12 return 0; 13 }
2.P1616瘋狂的採藥(完全揹包)洛谷傳送門
題目跟上一題採藥很像,但不一樣的是這次每種草藥不止一株而是有無數株,無限採所以是瘋狂採藥,言歸正傳這題聽完我的描述是不是覺得很眼熟,這不就是上一題我們強調倒推是所講的正推嗎,所以上程式碼(!!嚴禁抄襲,抄襲可恥!!)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 ll t,n,dp[10000005],w[10005],v[10005]; 5 int main() { 6 scanf("%ld%ld",&t,&n); 7 for(int i=1;i<=n;i++)scanf("%ld%ld",&w[i],&v[i]); 8 for(int i=1;i<=n;i++){ 9 for(int j=w[i];j<=t;j++)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//只有這的for不一樣 10 } 11 printf("%lld",dp[t]); 12 return 0; 13 }
最後說一句記得看資料範圍不開long long見祖宗
有錯誤歡迎指出,寫博不易點個讚唄