codeforces 730 J. Bottles

本文介绍了一种解决特定0-1背包问题的方法,即如何从n个瓶子中选择k个,使得这些瓶子的原有水量之和最大,同时k值最小。通过记忆化搜索算法实现了最优解。

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

这个题目题意很好理解,说的是将n个水甁中剩余的水全部倒进k个瓶子中。其中,让k最小,而且所需要倒的水最少。简单的说,就是在保证k最小的前提下,让k个瓶子中原有的水量之和最大。

想这个题目的时候也想了好一会,其实看清本质,就是一个0-1背包的问题。首先,k值最小不难确定,这里就不再多说。确定k值后,就是要确定出,这个n个瓶子中,选择哪k个才是符合要求的。对于每个瓶子,无非就是选还是不选的问题。那么,这个就时典型的0-1背包了。对于第i个,只需要考虑选还是不选所造成的影响(这里表现为所得到的不需要移动的水的体积),然后二者取一个最大值即可。这里,对于选还是不选,我门需要根据当前状态判定一下,选i是不是必须的。如果不是,才去判断是否需要选者。判断的方法也很简单,假设当前的状态为i, j, v(前i个中选者j个瓶子来装v体积的水),那么需要判断一下前i个瓶子中,最大的j个瓶子能不能装下v体积的水即可。代码如下,由于使用记忆化搜索,所以比较好理解。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>

using namespace std;

const int MAX = 101;

struct Node{
	int a, b;

	bool operator < (const Node& args) const{
		return b < args.b;
	}
}p[MAX];

int c[MAX][MAX], d[MAX][MAX][MAX*MAX];

int dfs(int n, int k, int v){

	//printf("%d, %d, %d, %d\n", n, k, v, c[n-1][k]);

	
	if (k > n) return 0;
	if (k <= 0) return 0;

	if (n < 1) return 0;

	if (v <= 0) return 0;

	if (d[n][k][v] != -1) return d[n][k][v];

	d[n][k][v] = dfs(n-1, k-1, v-p[n].b) + p[n].a;
	

	if (c[n-1][k] >= v){
		d[n][k][v] = max(d[n][k][v], dfs(n-1, k, v));
	}

	return d[n][k][v];

}



int main(){

	int n;

	while (scanf("%d", &n) != EOF){

		int v = 0;
		for (int i = 1; i<=n; i++){
			scanf("%d", &p[i].a);
			v += p[i].a;
		}

		for (int i = 1; i<=n; i++){
			scanf("%d", &p[i].b);
		}


		sort(p+1, p+n+1);

		//for (int i = 1; i<=n; i++) printf("%d  %d  %d\n",i, p[i].a, p[i].b);

		int k = 0, tmp = 0;

		for(int i = n; i>=1 && tmp < v; i--) {
			tmp += p[i].b;
			k++;
		}

		memset(c, 0, sizeof(c));
		memset(d, -1, sizeof(d));

		for (int i = 1; i<=n; i++){
			c[i][1] = p[i].b;
			for (int j = 2; j<=i; j++){
				c[i][j] = c[i][j-1] + p[i-j+1].b;
			}
		}


		/*for (int i = 1; i<=n; i++){
			for (int j = 1; j<=i; j++){
				printf("%d ", c[i][j]);
			}
			printf("\n");
		}*/

		printf("%d %d\n", k, v - dfs(n, k, v));


	}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值