个人专题训练——背包dp(持续更新中)

A - Proud Merchants   HDU - 3466(带限制的01背包)

题意: 给你m元,去买大米,每袋大米给你p,q,v 当你手中的钱 >= q时,才能买价格为p的这袋大米,它的价值是v,求最大价值。

01背包的转移方程根据题意很容易写出来,但是会有问题。

for (int i = 1; i <= n; ++i)
  for (int j = m; j >= q[i]; --j)
    dp[j] = max(dp[j], dp[j - p[i]] + v[i]);
考虑到了可能存在排序的问题,就按 q < 排序了,过样例,但是WA了。如果买同样的东西,肯定先买q[i]大的,才能保证还能有再买东西的可能。如何让买的东西多上加多呢,还需要买p[i]便宜的,那手中的钱减小的速度就变慢了,这样也能保证还能存在继续买东西的可能。所以最优策略应该是先买q[i] - p[i]最大的。又因为状态转移方程是逆序更新的,所以按q[i] - p[i] < 排序。

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <cstring>
 4 using namespace std;
 5 const int N = 500 + 5;
 6 const int M = 5000 + 5;
 7
 8 int n, m, dp[M];
 9
10 struct node {
11     int p, q, v;
12 } a[N];
13
14 bool cmp(node a, node b) {
15     return a.q - a.p < b.q - b.p;  // 关键点
16 }
17
18 int main() {
19     while (~scanf("%d%d", &n, &m)) {
20         memset(dp, 0, sizeof(dp));
21         for (int i = 1; i <= n; ++i)
22             scanf("%d%d%d", &a[i].p, &a[i].q, &a[i].v);
23         sort(a + 1, a + n + 1, cmp);
24         for (int i = 1; i <= n; ++i)
25             for (int j = m; j >= a[i].q; --j)
26                 dp[j] = max(dp[j], dp[j - a[i].p] + a[i].v);
27         // for (int i = 1; i <= m; ++i)
28         //     printf("dp[%d]=%d%s", i, dp[i], i == m ? "\n" : " ");
29         printf("%d\n", dp[m]);
30     }
31     return 0;
32 }

C - Piggy-Bank   HDU - 1114(带限制的完全背包)

题意:给出n个硬币的价值和体积,问你能否正好装满背包,若能装满,求总价值的最小值

一般背包要求求最小值,这里初始化为INF,然后用一维的完全背包的方法直接更新就行

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int INF = 0x3f3f3f3f;
 6 const int N = 500 + 5;
 7 const int M = 1e4 + 5;
 8
 9 int E, F, n;
10 int dp[M], p[N], w[N];
11
12 int main() {
13     int T; scanf("%d", &T);
14     while (T--) {
15         scanf("%d%d%d", &E, &F, &n);
16         for (int i = 1; i <= n; ++i)
17             scanf("%d%d", &p[i], &w[i]);
18         memset(dp, INF, sizeof(dp));
19         dp[0] = 0;
20         for (int i = 1; i <= n; ++i)
21             for (int j = w[i]; j <= F - E; ++j)
22                 dp[j] = min(dp[j], dp[j - w[i]] + p[i]);
23         if (dp[F - E] != INF)
24             printf("The minimum amount of money in the piggy-bank is %d.\n", dp[F - E]);
25         else
26             printf("This is impossible.\n");
27     }
28     return 0;
29 }

D - I love sneakers!   HDU - 3033(分组背包变形)

背包九讲中的分组背包,是每组中最多买一件,而这道题是每组中至少买一件

背包九讲给出的分组背包伪代码,其实就是分组的01背包。

但是这道题,需要对当前情况进行判断是否合法,必须由合法的情况更新过去。

我们考虑两种情况,当前物品是否是本组中第一次被选的,是第一次,就可以由上一组更新这一组

不是第一次,就可以组内更新,相当于组内的01背包。

还要注意初始化的问题,需要将所有不和法的情况置为-1

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 #include <vector>
 5 using namespace std;
 6 const int M = 1e4 + 5;
 7
 8 int dp[15][M], n, m, k;
 9 vector < pair<int, int> > vec[15];
10
11 int main() {
12     while (~scanf("%d%d%d", &n, &m, &k)) {
13         for (int i = 0; i < 15; ++i) vec[i].clear();
14         for (int i = 1; i <= n; ++i) {
15             int a, b, c;
16             scanf("%d%d%d", &a, &b, &c);
17             vec[a].push_back(make_pair(b, c));
18         }
19         memset(dp, -1, sizeof(dp));
20         memset(dp[0], 0, sizeof(dp[0]));
21         for (int i = 1; i <= k; ++i)
22             for (int x = 0; x < (int) vec[i].size(); ++x) {
23                 int p = vec[i][x].first;
24                 for (int j = m; j >= p; --j) {
25                     int v = vec[i][x].second;
26                     if (dp[i][j - p] != -1)
27                         dp[i][j] = max(dp[i][j - p] + v, dp[i][j]);
28                     if (dp[i - 1][j - p] != -1)
29                         dp[i][j] = max(dp[i][j], dp[i - 1][j - p] + v);
30                 }
31             }
32         if (dp[k][m] != -1)
33             printf("%d\n", dp[k][m]);
34         else
35             printf("Impossible\n");
36     }
37     return 0;
38 }

E - AreYouBusy   HDU - 3535(混合背包)

给你n个集合,刚开始有m元。 所有数据范围都 <= 100
下面对n个集合的描述输入 x,s   表示第i个集合有x件商品,s描述该集合的性质
(s == 0:至少买一件   s == 1:至多买一件   s == 2:数量没限制)
下面x行,给出p,v    给出第i件商品的价格和价值。

思想和上面的D题差不多,因为当前组需要通过上一组的合法状态得到,或者由组内的合法状态得到

我们考虑3种情况:

对于3种情况如何初始化也是一个问题:

对于至少买1件的,一开始本组都是不合法的,而对于其他两种情况,是站在前面的情况下更新本组的,相当于继承前一组的状态,所以要复制前一组的内容

如果用以上的方法写,还需要注意本题最坑的一点,就是存在p为0的情况。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 #include <cstring>
 5 #include <vector>
 6 using namespace std;
 7 const int M = 100 + 5;
 8 const int N = 100 + 5;
 9
10 int n, m, s[N];
11 vector < pair <int, int> > vec[N];
12 int dp[N][M];
13
14 int main() {
15     //freopen("in.txt", "r", stdin);
16     while (~scanf("%d%d", &n, &m)) {
17         for (int i = 0; i < N; ++i) vec[i].clear();
18         int x, y, p, v;
19         for (int i = 1; i <= n; ++i) {
20             scanf("%d%d", &x, &y);
21             s[i] = y;
22             while (x--) {
23                 scanf("%d%d", &p, &v);
24                 vec[i].push_back(make_pair(p, v));
25             }
26         }
27         memset(dp, 0, sizeof(dp));
28         for (int i = 1; i <= n; ++i) {
29             if (s[i])
30                 for (int t = 0; t <= m; ++t) dp[i][t] = dp[i - 1][t];
31             else
32                 for (int t = 0; t <= m; ++t) dp[i][t] = -1;
33             for (int x = 0; x < (int) vec[i].size(); ++x) {
34                 int p = vec[i][x].first;
35                 int v = vec[i][x].second;
36                 if (s[i] == 0) {
37                     for (int j = m; j >= p; --j) {
38                         // 下面两个if的顺序不能错,因为可能存在p为0的情况。
39                         // 如果第二个if更新成功,那么再进行第一个if,自身就合法了,就一定会再次更新,一个物品选择了两次
40                         // 注意一点:dp[i][j]自身是不合法的,需要通过合法的情况进行更新
41                         if (dp[i][j - p] != -1)
42                             dp[i][j] = max(dp[i][j], dp[i][j - p] + v);
43                         if (dp[i - 1][j - p] != -1)
44                             dp[i][j] = max(dp[i][j], dp[i - 1][j - p] + v);
45                     }
46                 } else if (s[i] == 1) {
47                     for (int j = m; j >= p; --j) {
48                         if (dp[i - 1][j - p] != -1)
49                             dp[i][j] = max(dp[i][j], dp[i - 1][j - p] + v);
50                     }
51                 } else {
52                     for (int j = m; j >= p; --j) {
53                         if (dp[i][j - p] != -1)
54                             dp[i][j] = max(dp[i][j], dp[i][j - p] + v);
55                     }
56                 }
57             }
58         }
59         printf("%d\n", dp[n][m]);
60     }
61     return 0;
62 }

对于本题数据范围小,而且存在p为0的情况,可以通过初始化为-INF,来直接避免考虑细节,详见代码,而且组的顺序不影响dp,代码量减少很多

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <cstring>
 4 using namespace std;
 5 const int N = 100 + 5;
 6 const int INF = 0x3f3f3f3f;
 7
 8 int n, m, s, x;
 9 int dp[N][N], p[N], v[N];
10
11 int main() {
12     //freopen("in.txt", "r", stdin);
13     while (~scanf("%d%d", &n, &m)) {
14         memset(dp, 0, sizeof(dp));
15         for (int i = 1; i <= n; ++i) {
16             scanf("%d%d", &x, &s);
17             for (int j = 1; j <= x; ++j) scanf("%d%d", &p[j], &v[j]);
18             for (int j = 0; j <= m; ++j)
19                 dp[i][j] = s ? dp[i - 1][j] : -INF;
20             for (int k = 1; k <= x; ++k) {
21                 if (s == 0) {
22                     for (int j = m; j >= p[k]; --j)
23                         dp[i][j] = max(dp[i][j], max(dp[i][j - p[k]] + v[k], dp[i - 1][j - p[k]] + v[k]));
24                 } else if (s == 1) {
25                     for (int j = m; j >= p[k]; --j)
26                         dp[i][j] = max(dp[i][j], dp[i - 1][j - p[k]] + v[k]);
27                 } else {
28                     for (int j = m; j >= p[k]; --j)
29                         dp[i][j] = max(dp[i][j], dp[i][j - p[k]] + v[k]);
30                 }
31             }
32         }
33         printf("%d\n", max(dp[n][m], -1));
34     }
35     return 0;
36 }

F - CD   UVA - 624(记录路径的01背包)

给你n个数,和一个sum,让你找这n个数的一个序列,使得和最接近n

参照背包九讲中的方法,这里我们用一维的01背包写,用一维是因为写了二维的时候发现,二维最后存的情况会少,所以二维的意义也只是求个maxn。

记录方法和上面讲的差不多,但是题目要求的是顺序给出选择的数。如果常规地顺序遍历数组,最后还需要从后往前逆推出答案存到vector或者数组中,

还要再逆序输出。不妨背包遍历数组的时候,就逆序遍历,就可以正推答案,也省去了存入的操作,可以直接输出。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int M = 1e4 + 5;
 6
 7 int dp[M], vis[25][M];
 8 int a[25], n, m;
 9
10 int main() {
11     freopen("in.txt", "r", stdin);
12     while (~scanf("%d%d", &n, &m)) {
13         for (int i = 1; i <= m; ++i) scanf("%d", &a[i]);
14         memset(dp, 0, sizeof(dp));
15         memset(vis, 0, sizeof(vis));
16         for (int i = m; i >= 1; --i)
17             for (int j = n; j >= a[i]; --j) {
18                 dp[j] = max(dp[j], dp[j - a[i]] + a[i]);
19                 if (dp[j] == dp[j - a[i]] + a[i])
20                     vis[i][j] = 1;
21             }
22         int j = n;
23         for (int i = 1; i <= m; ++i)
24             if (vis[i][j]) {
25                 printf("%d ", a[i]);
26                 j -= a[i];
27             }
28         printf("sum:%d\n", dp[n]);
29     }
30     return 0;
31 }

G - Dividing coins   UVA - 562(转化成01背包)

给你n个数,让你把它分成两堆,使得两堆的和之差尽量小,求这个差值。

换个说法就是,既然可以分成两堆,那么就是找存在和为sum的方案数,以及和为n - sum的方案数也存在的情况,使abs(2 * sum - n)尽量小

背包九讲中说:直接把01背包那里的max改为sum就行,因为已经遍历了所有可能的组成情况。这样题目就很简单了。dp[i] 表示和为 i 的方案数。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 #include <cstring>
 5 using namespace std;
 6 const int N = 100 + 5;
 7 const int M = 5e4 + 5;
 8
 9 int a[N];
10 int dp[M];
11
12 int main() {
13     int t; scanf("%d", &t);
14     while (t--) {
15         int n; scanf("%d", &n);
16         int sum = 0;
17         for (int i = 1; i <= n; ++i) {
18             scanf("%d", &a[i]);
19             sum += a[i];
20         }
21         memset(dp, 0, sizeof(dp));
22         dp[0] = 1;
23         for (int i = 1; i <= n; ++i)
24             for (int j = sum; j >= a[i]; --j)
25                 dp[j] += dp[j - a[i]];
26         int ans = sum;
27         for (int i = 1; i <= sum; ++i)
28             if (dp[i] && dp[sum - i])
29                 ans = min(ans, abs(sum - i * 2));
30         printf("%d\n", ans);
31     }
32     

H - Dollars   UVA - 147(转化成完全背包)

给你很多种面值的硬币,让你求构成面值和为sum的方案数,硬币数量不限制

和G题基本一样,就是把换成完全背包的写法,状态转移方程不变。还有一个坑点,就是浮点数转化成整数有误差。

 1 /*
 2  double 保证小数点后15位准确
 3  坑点:  18.90 * 100 = 1889 (精度误差得可怕)
 4  无误差的方法(一般不用):
 5      (tmp * 1000 - tmp * 100) / 9;
 6  更常用的方法:
 7       (tmp + eps) * 100    eps足够小
 8 */
 9 #include <cstdio>
10 #include <cstring>
11 using namespace std;
12 const double eps = 1e-6;
13 const int M = 3e4 + 5;
14
15 int m;
16 long long dp[M];
17 int a[11] = {5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000};
18
19 void init() {
20     dp[0] = 1;
21     for (int i = 0; i < 11; ++i)
22         for (int j = a[i]; j <= 30000; ++j)
23             dp[j] += dp[j - a[i]];
24 }
25
26 int main() {
27     init();
28     double tmp;
29     while (~scanf("%lf", &tmp) && tmp) {
30         m = (tmp + eps) * 100;
31         printf("%6.2lf%17lld\n", tmp, dp[m]);
32     }
33     return 0;
34 }

时间: 2024-10-25 22:30:09

个人专题训练——背包dp(持续更新中)的相关文章

linux学习资料持续更新中

一.LINUX基础教程 1.老男孩系列免费视频: 1) linux高薪入门实战视频教程(第二部)老男孩linux教程 http://edu.51cto.com/course/course_id-1035-page-1.html 2) 跟着老男孩从0开始一步步实战深入学习linux运维(三) http://edu.51cto.com/lesson/id-11909.html linux学习资料持续更新中,布布扣,bubuko.com

Hello World!的各种编程语言程序(持续更新中……)

对于很多学习编程语言新手们,可能接触到的第一个程序就是"Hello World"的输出程序,笔者想在此篇简短的博文中介绍关于各种编程语言的"Hello World"输出程序. 至今,笔者仅仅接触过C++和Python两种编程语言,而且都仅仅是新手,所以此次只能写C++和Python两种语言的"Hello World"输出程序,但此篇博文会随着笔者学习的编程语言种类的增多而不断完善. 1. C++语言 #include<iostream>

阿里笔试题(2015)持续更新中

第一次做阿里笔试题,除了ACM题之外从来没有做过校招网络题呀,完全是裸考,总体感觉吧,对于我来说,感觉时间不够用,不是题不会,感觉时间紧,大脑很混乱,总结这一次的笔试题 废话不多说,直接上题和答案 平均每个人逗留时间为20分钟,那么开场前20分钟一共来了400人,且有20个人逗留时间已经到,但他们不一定出去,注意是平均时间,所有博物馆最少应该容纳500人 双向循环列表,从任何一个元素开始可以遍历全部元素 先和后面的元素相连 s->next=p->next; p->next->pre

Atom使用记录(持续更新中)

部分内容取自:http://www.jianshu.com/p/dd97cbb3c22d,我自己也在使用,持续更新中 Atom安装插件在窗口中File---Setting---install 在里面进行搜索就行. minimap: 为Atom加上一个代码预览地图,就想sublime中右侧的缩略图一样,效果如图. Emmet(和sublime一样的) simplified-chinese-menu:Atom的简体中文语言包,完整汉化,兼容所有已发布的版本Atom. autoclose-html:h

老男孩高端linux运维在线课程视频全套,持续更新中!

老男孩高端linux运维在线课程视频全套,持续更新中 http://edu.51cto.com/course/course_id-5651.html

资源向导之 JOS 计划 #持续更新中# MIT 6.828

JOS 计划 #持续更新中# 童鞋,上网要科学上网,做lab也要科学的做. 之前我一上来就做实验,很多资料都不知道.现在打算重新来过 方法: 0.xv6源码不要用MIT官网的那份,我的主机是Linux/Ubuntu 14.0各种编译error,我都改的想吐.后来直接用github上别人改好的,直接能跑起来没有编译错误的xv6. 1.按照MIT给出的课程安排表,每一次课的相关lecture必须全部过一遍. 2.要求的课堂作业必须完成,很多时候课程要求的任务是很轻松的,只要修改部分代码就行了.这里我

shell 常用文件、字符串、二元整数测试操作符-持续更新中

常用的文件测试操作符-持续更新中 -e--exist 文件存在为真 -f--file 文件存在且为普通文件为真 -d--directory 文件存在且为目录为真 -s--size 文件存在且大小不为零为真 -r--read 文件存在且可读为真 -w--write 文件存在且可写为真 -x--executable 文件存在且可执行为真 -L--link 文件存在且为链接文件则为真 f1 -nt f2--new than f1比f2新则为真 f1 -ot f2--old than f1比f2旧则为真

C 语言的若干问题(持续更新中)

mnesia在频繁操作数据的过程可能会报错:** WARNING ** Mnesia is overloaded: {dump_log, write_threshold},可以看出,mnesia应该是过载了.这个警告在mnesia dump操作会发生这个问题,表类型为disc_only_copies .disc_copies都可能会发生. 如何重现这个问题,例子的场景是多个进程同时在不断地mnesia:dirty_write/2 mnesia过载分析 1.抛出警告是在mnesia 增加dump

ansible部署及应用--持续更新中

1.简介 ansible是新出现的自动化运维工具,基于Python开发,集合了众多运维工具(puppet.cfengine.chef.func.fabric)的优点,实现了批量系统配置.批量程序部署.批量运行命令等功能.ansible是基于模块工作的,本身没有批量部署的能力.真正具有批量部署的是ansible所运行的模块,ansible只是提供一种框架.主要包括: (1).连接插件connection plugins:负责和被监控端实现通信: (2).host inventory:指定操作的主机