http://www.javashuo.com/article/p-zqreiczo-ex.htmlphp
dp自從知道有這麼個東西時,就沒有好好的學,,如今一看道dp的題就繞道走,,,可是,不少比賽中的dp問題有不少,,別人都會,本身不會很吃虧啊,,,因而從基礎開始一點一點的補inghtml
揹包問題是動態規劃的經典也是基礎,,,下面的東西部分來自 揹包九講;ios
01揹包指的是對於任意的物品只有 取或不取 兩種狀態,,c++
狀態轉移方程爲:數組
\(F[i,j]=max(F[i-1,j], F[i-1,j-c_i]+w_i)\)優化
外層循環枚舉物品總數:\(for \ i=1\ to\ n\)ui
內層循環枚舉揹包的容量: \(for \ j=c_i \ to \ v\)spa
空間優化後的狀態轉移方程:.net
\(F[j]=max(F[j], F[j-c_i]+w_i)\)code
外層循環不變,內層循環變爲: \(for \ j=v \ to \ c_i\)
外層循環能夠繼續優化爲: \(for \ j \ to \ max(v-\sum_i^nw_i, \ \ c_i)\)
初始化F數組就是在沒有任何物品能夠放入揹包時的合法狀態,因此,前者只有容量爲零的揹包什麼都不裝的狀況下是剛好裝滿的,其餘容量的揹包都是未定義的狀態,無合法解;後者由於沒必要裝滿,因此什麼都不裝的時候就是一個合法解,這時的價值爲零。
裸的01揹包,,直接作,,,注意判斷當前物品是否能放入揹包,,再選擇放與不放,,
還有內層循環容量的遍歷是從0開始
memset(dp, 0, sizeof dp); for(int i = 1; i <= n; ++i) for(int j = 0; j <= v; ++j) if(c[i] <= j)//能放入時,選擇放與不放 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + w[i]); else dp[i][j] = dp[i - 1][j]; printf("%d\n", dp[n][v]);
空間優化後的方法:
memset(dp, 0, sizeof dp); for(int i = 1; i <= n; ++i) for(int j = v; j >= 0; --j) if(c[i] <= j)//能放入時,選擇放與不放 dp[j] = max(dp[j], dp[j - c[i]] + w[i]); printf("%d\n", dp[v]);
題意是:一個總錢數爲m的錢包,在剩餘金額大於等於5的狀況下能夠購買任何東西,即便買了一個東西后剩餘錢數爲負,而後給你這n個東西的標價,每種東西只能購買一次,,
這道題按01揹包作的話,能夠將錢包m當作揹包的容量,n道菜就是n種物品, 每種物品的價值和花費都是其菜價,,
這是其中一個點,還有爲了儘量的是利益最大,,咱們能夠先保留5塊,爲了最後買那個最貴的菜,,對剩下的n-1個菜選擇出價值最大的,,,這樣就將這道題轉化成了容量爲m-5的揹包選擇一些物品使得總價值最大,,,最後的答案在算上那個最貴的菜就好了,,,
int dp[maxn], c[maxn], w[maxn]; int main() { // freopen("233.in" , "r" , stdin); // freopen("233.out" , "w" , stdout); // ios_base::sync_with_stdio(0); // cin.tie(0);cout.tie(0); int n; while(scanf("%d", &n) && n) { for(int i = 1; i <= n; ++i) scanf("%d", &c[i]); int m;scanf("%d", &m); if(m < 5) { printf("%d\n", m); continue; } m -= 5; sort(c + 1, c + 1 + n); memset(dp, 0, sizeof dp); for(int i = 1; i <= n - 1; ++i) for(int j = m; j >= c[i]; --j) dp[j] = max(dp[j], dp[j - c[i]] + c[i]); printf("%d\n", m + 5 - dp[m] - c[n]); } return 0; }
題意是:有一些設施,每一個設施的價值爲 \(w_i\),,而後要分紅兩堆,這兩堆的價值要儘量的相近
顯然分後的價值和 \(sum\) 就是原來的價值和,,而後確定一個大於等於均值,一個小於等於,,,因此能夠將這道題目當作01揹包的模型:一個容量爲 \(sum/2\) 的揹包,選擇裝一些物品,這些物品的價值的和費用相同,,求最大的價值
int dp[maxn], c[maxn], w[maxn]; int main() { // freopen("233.in" , "r" , stdin); // freopen("233.out" , "w" , stdout); // ios_base::sync_with_stdio(0); // cin.tie(0);cout.tie(0); int n; while(scanf("%d", &n) && n > 0) { int tot = 0; for(int i = 1; i <= n; ++i) { int a, b; scanf("%d%d", &a, &b); while(b--)w[++tot] = a; } memset(dp, 0, sizeof dp); int sum = 0; for(int i = 1; i <= tot; ++i)sum += w[i]; for(int i = 1; i <= tot; ++i) for(int j = sum / 2; j >= w[i]; --j) dp[j] = max(dp[j], dp[j - w[i]] + w[i]); printf("%d %d\n", sum - dp[sum / 2], dp[sum / 2]); } return 0; }
徹底揹包就是在01揹包的基礎上對於物品的限制解除,,物品再也不爲只能取一件,而是無限件(實際也不多是無限件,每個物品最多取 \(\lfloor \frac{v}{c_i} \rfloor\)),,
將徹底揹包轉化爲01揹包後, 狀態轉移方程和01揹包的相似,,只有對揹包容量的枚舉也就是內層循環中,徹底揹包是遞增的順序而01揹包的是遞減的順序,,
\(for \ j=c_i \ to \ v\)
從二維數組上區別0-1揹包和徹底揹包也就是狀態轉移方程就差異在放第i中物品時,徹底揹包在選擇放這個物品時,最優解是F[i][j-c[i]]+w[i]即畫表格中同行的那一個,而0-1揹包比較的是F[i-1][j-c[i]]+w[i],上一行的那一個。
從一維數組上區別0-1揹包和徹底揹包差異就在循環順序上,0-1揹包必須逆序,由於這樣保證了不會重複選擇已經選擇的物品,而徹底揹包是順序,順序會覆蓋之前的狀態,因此存在選擇屢次的狀況,也符合徹底揹包的題意。狀態轉移方程都爲F[i] = max(F[i],dp[F-c[i]]+v[i])。
題意是:給你一個存錢罐的總質量個單純存錢罐的質量(也就是差爲錢的質量),,以及n種硬幣的面值和質量,而後問你最小的金額是多少
差值能夠看做揹包的容量,每一個硬幣的質量爲物品的代價,面值爲其價值,,而後求最小的價值轉移方程裏就爲min,,初始化再改變一下,,
int dp[maxn], c[maxn], w[maxn]; int main() { // freopen("233.in" , "r" , stdin); // freopen("233.out" , "w" , stdout); // ios_base::sync_with_stdio(0); // cin.tie(0);cout.tie(0); int t;scanf("%d", &t); while(t--) { int e, f;scanf("%d%d", &e, &f); int v = f - e; int k;scanf("%d", &k); for(int i = 1; i <= k; ++i)scanf("%d%d", &w[i], &c[i]); memset(dp, inf, sizeof dp); dp[0] = 0; for(int i = 1; i <= k; ++i) for(int j = c[i]; j <= v; ++j) dp[j] = min(dp[j], dp[j - c[i]] + w[i]); if(dp[v] >= inf) printf("This is impossible.\n"); else printf("The minimum amount of money in the piggy-bank is %d.\n", dp[v]); } return 0; }
多重揹包就是徹底揹包的限制版,,每一種物品再也不是無限個,,而是給定的個數,最後仍是求揹包的最大價值什麼的,,,
轉化成01揹包問題就是對於每一種物品取 \(1, 2, 2^2, 2^3,,,2^{k-1},M_i-2^k+1\)件,,
通常的多重揹包模板:
int dp[maxn], c[maxn], w[maxn], num[maxn]; int n, m, v;//n爲物品總數,v爲揹包容量 //01揹包,該物品的代價,價值 void ZeroOnePack(int C, int W) { for(int i = v; i >= C; --i) dp[i] = max(dp[i], dp[i - C] + W); return; } //徹底揹包,該物品的代價,價值 void CompletePack(int C, int W) { for(int i = C; i <= v; ++i) dp[i] = max(dp[i], dp[i - C] + W); return; } //一次多重揹包,該物品的代價,價值,數量 void OneMuitPack(int C, int W, int M) { if(v <= C * M)//物品足夠多時用徹底揹包 { CompletePack(C, W); return; } else //不然用二進制劃分紅若干件01揹包的物品 { int k = 1; while(k < M) { ZeroOnePack(k * C, k * W);//某一個劃分紅01揹包的物品 M -= k; k <<= 1; } ZeroOnePack(C * M, W * M);//剩下的一些物品 } return; }
題意是:n種面值的硬幣,每種硬幣的個數限定,問你可以組成幾種面值和不超過m的組成方法,
轉化成揹包問題就是,一個容量爲m的揹包裝一些價值和代價都爲面值的物品,其中物品的個數有限制,,問揹包內的價值的可能種類
//cf //#include <bits/stdc++.h> #include <iostream> #include <cstdio> #include <cstdlib> #include <string.h> #include <algorithm> #define aaa cout<<233<<endl; #define endl '\n' #define pb push_back using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, ull> pii; const int inf = 0x3f3f3f3f;//1061109567 const ll linf = 0x3f3f3f3f3f3f3f; const double eps = 1e-6; const double pi = 3.14159265358979; const int maxn = 1e5 + 5; const int maxm = 2e5 + 5; const ll mod = 1e9 + 7; int dp[maxn], c[maxn], w[maxn], num[maxn]; int n, m, v;//n爲物品總數,v爲揹包容量 //01揹包,該物品的代價,價值 void ZeroOnePack(int C, int W) { for(int i = v; i >= C; --i) dp[i] = max(dp[i], dp[i - C] + W); return; } //徹底揹包,該物品的代價,價值 void CompletePack(int C, int W) { for(int i = C; i <= v; ++i) dp[i] = max(dp[i], dp[i - C] + W); return; } //一次多重揹包,該物品的代價,價值,數量 void OneMuitPack(int C, int W, int M) { if(v <= C * M)//物品足夠多時用徹底揹包 { CompletePack(C, W); return; } else //不然用二進制劃分紅若干件01揹包的物品 { int k = 1; while(k < M) { ZeroOnePack(k * C, k * W);//某一個劃分紅01揹包的物品 M -= k; k <<= 1; } ZeroOnePack(C * M, W * M);//剩下的一些物品 } return; } int main() { // freopen("233.in" , "r" , stdin); // freopen("233.out" , "w" , stdout); // ios_base::sync_with_stdio(0); // cin.tie(0);cout.tie(0); while(scanf("%d%d", &n, &m) && n + m) { for(int i = 1; i <= n; ++i)scanf("%d", &w[i]); for(int i = 1; i <= n; ++i)scanf("%d", &num[i]); memset(dp, 0, sizeof dp); v = m; for(int i = 1; i <= n; ++i) OneMuitPack(w[i], w[i], num[i]); int ans = 0; for(int i = 1; i <= m; ++i)if(dp[i] == i)++ans; printf("%d\n", ans); } return 0; }
混合揹包就是n種物品有的只能取一次,有的能取有限次,有的能取無限次,而後問你對於容量爲v的揹包的可取最大價值是多少
直接判斷每一個物品的種類,使用不一樣的揹包類型就好了
題意就是混合揹包的定義,,直接作就行
//cf //#include <bits/stdc++.h> #include <iostream> #include <cstdio> #include <cstdlib> #include <string.h> #include <algorithm> #define aaa cout<<233<<endl; #define endl '\n' #define pb push_back using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, ull> pii; const int inf = 0x3f3f3f3f;//1061109567 const ll linf = 0x3f3f3f3f3f3f3f; const double eps = 1e-6; const double pi = 3.14159265358979; const int maxn = 2e5 + 5; const int maxm = 2e5 + 5; const ll mod = 1e9 + 7; int dp[maxn], c[maxn], w[maxn], num[maxn]; int n, m, v;//n爲物品總數,v爲揹包容量 //01揹包,該物品的代價,價值 void ZeroOnePack(int C, int W) { for(int i = v; i >= C; --i) dp[i] = max(dp[i], dp[i - C] + W); return; } //徹底揹包,該物品的代價,價值 void CompletePack(int C, int W) { for(int i = C; i <= v; ++i) dp[i] = max(dp[i], dp[i - C] + W); return; } //一次多重揹包,該物品的代價,價值,數量 void OneMuitPack(int C, int W, int M) { if(v <= C * M)//物品足夠多時用徹底揹包 { CompletePack(C, W); return; } else //不然用二進制劃分紅若干件01揹包的物品 { int k = 1; while(k < M) { ZeroOnePack(k * C, k * W);//某一個劃分紅01揹包的物品 M -= k; k <<= 1; } ZeroOnePack(C * M, W * M);//剩下的一些物品 } return; } int main() { // ios_base::sync_with_stdio(0); // cin.tie(0);cout.tie(0); scanf("%d%d", &n, &v); for(int i = 1; i <= n; ++i)scanf("%d%d%d", &c[i], &w[i], &num[i]); memset(dp, 0, sizeof dp); for(int i = 1; i <= n; ++i) { if(num[i] == 1) ZeroOnePack(c[i], w[i]); else if(num[i] == -1) CompletePack(c[i], w[i]); else OneMuitPack(c[i], w[i], num[i]); } printf("%d\n", dp[v]); return 0; }
二維費用指的就是相比以前的揹包問題侑多了一個費用的影響因素,,對於一個物品有兩個不一樣的代價以及其容量,,作法和前面的同樣,dp數組增長一維就好了,,
轉化成揹包問題就是代價一是忍耐度,揹包容量爲m;代價二就是打怪,容量就是s,,求最大的價值(經驗值)與n的大小關係,,,
//cf //#include <bits/stdc++.h> #include <iostream> #include <cstdio> #include <cstdlib> #include <string.h> #include <algorithm> #define aaa cout<<233<<endl; #define endl '\n' #define pb push_back using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, ull> pii; const int inf = 0x3f3f3f3f;//1061109567 const ll linf = 0x3f3f3f3f3f3f3f; const double eps = 1e-6; const double pi = 3.14159265358979; const int maxn = 1e3 + 5; const int maxm = 2e5 + 5; const ll mod = 1e9 + 7; int dp[maxn][maxn], c[maxn], w[maxn], num[maxn]; int n, m, v;//n爲物品總數,v爲揹包容量 int main() { // freopen("233.in" , "r" , stdin); // freopen("233.out" , "w" , stdout); // ios_base::sync_with_stdio(0); // cin.tie(0);cout.tie(0); int n, m, k, s; while(~scanf("%d%d%d%d", &n, &m, &k, &s)) { memset(w, 0, sizeof w); memset(c, 0, sizeof c); memset(dp, 0, sizeof dp); for(int i = 1; i <= k; ++i) scanf("%d%d", &w[i], &c[i]); int ans = inf; for(int i = 1; i <= k; ++i) for(int j = c[i]; j <= m; ++j) for(int k = 1; k <= s; ++k) { dp[j][k] = max(dp[j][k], dp[j - c[i]][k - 1] + w[i]); if(dp[j][k] >= n)ans = min(ans, j); } if(ans > m)printf("-1\n"); else printf("%d\n", m - ans); } return 0; }
(loading)
分組揹包就是:一堆物品被劃分紅了K組,同一組的物品只能選擇一個,或者這組不選,其餘的條件和其餘揹包模型同樣,,
解決方法,再加一層對每組揹包的枚舉
僞代碼:
\(for \ k=1 \ to \ K\)
\(for \ v=V \ to \ V\)
\(for \ item \ i \ in \ group \ k\)
\(F[v]=max(F[v], F[v-C_i]+W_i)\)
題意就是有n節課,每一課上幾天的價值給你,,一共要上m節課,問最大的價值,,
把這道題當作容量爲m的揹包,裝分爲n組的物品最大的價值就行
int dp[maxn]; int main() { int n, m; int a[maxn][maxn]; while(scanf("%d%d", &n, &m) && n + m) { memset(a, 0, sizeof a); for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) scanf("%d", &a[i][j]); memset(dp, 0, sizeof dp); for(int k = 1; k <= n; ++k)//枚舉組數 for(int j = m; j >= 0; --j)//枚舉揹包的容量 for(int i = 1; i <= m; ++i)//枚舉第k組的物品 if(i <= j)//保證能裝下 dp[j] = max(dp[j], dp[j - i] + a[k][i]); printf("%d\n", dp[m]); } return 0; }
題意就是一堆鞋子,某一些是一個牌子的,而後每一雙鞋有一個價格(看做代價),一個價值,每一個牌子至少取一雙,問最大的價值,,,
與上一道不一樣的是每一組的物品再也不是最多選一個了,,一組能夠選多個,每一組都要選一個,,
dp[i][j]表示的是前i組在容量爲j的揹包所取的最大價值,,當前狀態dp[i][j]能夠由 前一狀態在本組選一個物品 推來,也能夠由 當前狀態在本組再取一個物品 推來,,
初始化也不一樣了,,除了那一組都不選的那一行dp爲零,,其餘都爲負,即未定義狀態,,由這個判斷是否有解,,
//hdu //#include <bits/stdc++.h> #include <iostream> #include <cstdio> #include <cstdlib> #include <string.h> #include <algorithm> #define aaa cout<<233<<endl; #define endl '\n' #define pb push_back using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> pii; const int inf = 0x3f3f3f3f;//1061109567 const ll linf = 0x3f3f3f3f3f3f3f; const double eps = 1e-6; const double pi = 3.14159265358979; const int maxn = 1e4 + 5; const int maxm = 2e5 + 5; const ll mod = 1e9 + 7; int dp[11][maxn]; pii a[11][maxn]; int num[11]; int main() { // freopen("233.in" , "r" , stdin); // freopen("233.out" , "w" , stdout); // ios_base::sync_with_stdio(0); // cin.tie(0);cout.tie(0); int n, m, K; while(~scanf("%d%d%d", &n, &m, &K)) { memset(num, 0, sizeof num); for(int i = 1; i <= n; ++i) { int aa, bb, cc; scanf("%d%d%d", &aa, &bb, &cc); ++num[aa]; a[aa][num[aa]].first = bb; a[aa][num[aa]].second = cc; } memset(dp, -1, sizeof dp); //for(int i = 0; i <= m; ++i)dp[0][i] = 0; memset(dp[0], 0, sizeof dp[0]); //不能寫成memset(dp[0], 0, sizeof dp); for(int k = 1; k <= K; ++k) for(int i = 1; i <= num[k]; ++i) for(int j = m; j >= a[k][i].first; --j) { if(dp[k][j - a[k][i].first] >= 0) dp[k][j] = max(dp[k][j], dp[k][j - a[k][i].first] + a[k][i].second); if(dp[k - 1][j - a[k][i].first] >= 0) dp[k][j] = max(dp[k - 1][j - a[k][i].first] + a[k][i].second, dp[k][j]); } if(dp[K][m] < 0)printf("Impossible\n"); else printf("%d\n", dp[K][m]); } return 0; }
這道題沒怎麼理解還,,
(loading)
剩下一些其餘的內容,暫時先放放,,