ZCMU 2165: 黄金矿工
Description
Input
3 10
1 1 1 1
2 2 2 2
1 3 15 9
Output
3
思路
通过阅读题目以及实地考察游戏我们可以发现,当多个黄金排列在同一直线上时,我们有多种取舍方式,假设有三块黄金在同一直线上:1.都不取,2.取第一块,3.取第一和第二块,4.全取完。为了便于解决问题,我们可以将四种情况转化为三个互斥的物品,第一个物品含有第一块的价值和时间,第二个物品含有前两块的价值和时间,第三个物品含有三块的价值和时间。我们要么不取,要么取其中之一,于是转化为中间带有一个特殊判断的01背包。由于新手上路,故先采用二维dp数组便于自己理解。
二维dp数组AC代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cmath>
#include <string>
#include <list>
#include <algorithm>
#include <set>
#include <list>
#include <stack>
#include <map>
#include <bitset>
//#define INF 0x3f3f3f3f
//#define MAX 1e9
//#define N 1000005
//#define ll long long
using namespace std;
struct info
{
//角度,深度,挖取时间,价值
double ang;
int s;
int t;
int v;
}gold[205];
int group[205];
int dp[205][40005];//由于需要跨多个物品记忆化搜索,故需要开二维
int N, T;
int cmp(struct info a,struct info b)
{
if (a.ang == b.ang)return a.s < b.s;
return a.ang < b.ang;
}
int main()
{
int x, y, t, v;
while (~scanf("%d%d", &N, &T))
{
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= N; i++)
{
scanf("%d%d%d%d", &x, &y, &t, &v);
gold[i] = { (x*1.0 / y),x*x + y * y,t,v };
}
//排序
sort(gold + 1, gold + N + 1, cmp);
//对各个金块进行分组
int num = 1;
group[1] = 1;
for (int i = 2; i <= N; i++)
{
//若有角度相同的金块出现,将有依赖条件的金块与前面的金块合并,并且分在同一组内
if (gold[i].ang == gold[i - 1].ang)
{
group[i] = num;
gold[i].t += gold[i - 1].t;
gold[i].v += gold[i - 1].v;
}
else
{
group[i] = i;
num = i;
}
}
//01背包模板,对所有物品进行枚举
for (int i = 1; i <= N; i++)
{
//从背包为空开始,对背包容量进行枚举
for (int j = T; j >= 0; j--)
{
if (gold[i].t <= j)
{
// 由于这是个分组背包,故对组内和组外情况分开讨论
if (i > 1 && group[i] == group[i - 1])
{
//同一组内的物品比较的dp值为整组物品都尚未放入时当前背包容量的最大价值
dp[i][j] = max(dp[i-1][j], dp[group[i] - 1][j - gold[i].t] + gold[i].v);
}
else
{
//标准01背包比较形式
dp[i][j] = max(dp[i-1][j], dp[i - 1][j - gold[i].t] + gold[i].v);
}
}
//空间不够时要把之前的数据搬过来
else
{
dp[i][j] = dp[i - 1][j];
}
}
}
printf("%d\n", dp[N][T]);
}
return 0;
}
在oj上跑完二维的代码后,发现二维代码占用空间直逼33000,耗时56,故希望使用一维dp数组优化,在参考各大佬博客时发现了这么一个做法:
不将背包分组,仅对同组内物品的打表范围进行限制。在一维dp的打表中,我们对背包容量j的反向遍历止于物品i的体积,但若出现组内物品,则反向遍历提前止于物品i体积与其前置物品体积之和,也就是说我们必须要有能拿完其前置物品的充足空间才可考虑拿当前物品。思路骚气,但本蒟目前还未理解为何当空出充足空间后,有把握之前的最优解是取过其前置物品的解。这串代码提交后,由于二维化一维,并减少了一个分组数组,空间占用立刻降至1644,耗时也缩减至12,十分强悍。
骚操作AC代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cmath>
#include <string>
#include <list>
#include <algorithm>
#include <set>
#include <list>
#include <stack>
#include <map>
#include <bitset>
//#define INF 0x3f3f3f3f
//#define MAX 1e9
//#define N 1000005
//#define ll long long
using namespace std;
struct info
{
//角度,深度,挖取时间,价值
double ang;
int s;
int t;
int v;
}gold[205];
int dp[40005];
int N, T;
int cmp(struct info a,struct info b)
{
if (a.ang == b.ang)return a.s < b.s;
return a.ang < b.ang;
}
int main()
{
int x, y, t, v;
while (~scanf("%d%d", &N, &T))
{
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= N; i++)
{
scanf("%d%d%d%d", &x, &y, &t, &v);
gold[i] = { (x*1.0 / y),x*x + y * y,t,v };
}
//排序
sort(gold + 1, gold + N + 1, cmp);
//01背包模板,对所有元素进行枚举
for (int i = 1; i <= N; i++)
{
int time = 0;
//若出现相同角度的元素,则其必须在取到前两组的时间后才可取
if (gold[i].ang == gold[i - 1].ang)
{
time += gold[i].t;
}
else time = gold[i].t;
//从背包为空开始,对背包容量进行枚举
for (int j = T; j >= time; j--)
{
dp[j] = max(dp[j], dp[j - gold[i].t] + gold[i].v);
}
}
printf("%d\n", dp[T]);
}
return 0;
}