zcmu 黄金矿工 (分组背包)

博客内容介绍了编程竞赛中的Problem J: 黄金矿工,这是一个关于分组背包的问题。题目要求在限制条件下选择金子,同一直线上的金子必须按顺序拿取。通过将问题转化为分组背包问题,可以利用斜率进行分组,然后应用模板解决。博客提供了AC代码作为解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem J: 黄金矿工

Time Limit: 1 Sec  Memory Limit: 128 MB
Submit: 81  Solved: 39
[Submit][Status][Web Board]

Description

Input

3 10 

1 1 1 1 

2 2 2 2 

1 3 15 9

Output

3

 

Sample Input

Sample Output

HINT

 

-----sample2------

 

1 1 13 1

2 2 2 2

1 3 4 7

 

-----sample2-----

 

7

 

-----------

 

30%的数据,0 < T ≤ 4000 

 

100%的数据,N ≤ 200, 0 < T ≤ 40000 

题目大意:

对于同一条直线上的金子,必须把前面的拿走才能拿后面的。举个例子,有 1,2,3,三块金子,有拿1,拿 1 2,拿1 2 3三种拿法,同时他们之间是互斥的,三种拿法中只能取一种拿法。这样就变成了分组背包问题。

思路

变成分组背包问题后,接下来就是怎么分组了,很明显按直线的斜率分组,同一直线上的物品为同一组,接下来就是套模板了

我在代码里注释了。

AC代码:

#include<iostream>//分组背包
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
const int MAX = 500;
using namespace std;
struct node
{
	double k;
	int x, y, t, v;
}gold[MAX];//存物品
bool cmp(node x, node y)//同一直线上的排在一起
{
	if (x.k = y.k)
		return x.y<y.y;
	return x.k<y.k;
}
int f[40005], t[40005];//f数组存最大价值,t数组用来标记哪些是同一组的
int main()
{
	int n, T;
	while (scanf("%d %d", &n, &T) != EOF)
	{
		memset(f, 0, sizeof(f));//初始化数组
		memset(t, 0, sizeof(t));
		for (int i = 0; i<n; i++)
		{
			scanf("%d %d %d %d", &gold[i].x, &gold[i].y, &gold[i].t, &gold[i].v);
			gold[i].k = gold[i].x*1.0 / gold[i].y;//计算斜率,判断是否在同一条直线上。
		}
		sort(gold, gold + n, cmp);//排序
		int flag = 0;
		for (int i = 0; i<n; i++)//标记哪些物品是同一组的
		{
			if (gold[i].k == gold[i - 1].k)
				t[i] = flag;
			else
				t[i] = ++flag;
		}
		int p = 0, q;
		/*多重背包模板:
		for 所有的组k
		for v=V..0
		for 所有的i属于组k
		f[v]=max{f[v],f[v-c[i]]+w[i]}
		*/
		for (int i = 1; i <= flag; i++)//总有flag个组
		{
			for (int j = T; j>0; j--)//容积逆序循坏
			{
				int time = 0, v = 0;
				for (int z = p; z<n; z++)//循环组内的物品
				{
					time += gold[z].t;
					v += gold[z].v;
					if (j >= time)//j小于time就不用更新了。
						f[j] = max(f[j], f[j - time] + v);
					//  cout<<j<<" "<<f[j]<<" "<<z<<endl;
					if (t[z + 1] != t[z])//这一组的物品循环完了
					{
						q = z + 1;//记录下一组开始的位置
						break;
					}
				}
			}
			p = q;//更新开始的位置
		}
		cout << f[T] << endl;

	}


	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值