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)
剩下一些其余的内容,暂时先放放,,