codeforces 730 J Bottles

31 篇文章 0 订阅

Problem

codeforces.com/problemset/problem/730/J

题意

n 瓶水,每瓶水量 ai,容量 bi。要将所有水装到尽量少的瓶子内。

每移动一单位的水要消耗一单位时间,在最少瓶子的前提下,问移动水所需的最短时间。

Analysis

所需最少瓶子数可以贪心地求出,优先装容量大的瓶子。

DP。最初想的状态是:dp[i][j][k]:前 i 个瓶子里挑 j 个,总水量是 k 时的最短移动时间。然后找不到转移方程。

换状态:因为移一个单位水消耗一单位时间,知道需要 k 个瓶子后,时间就是:“总水量 - 这k个瓶子原有的水量和”,那么就可以找 k 个总容量够装所有水,且已有水量总和最大的瓶子。

定义状态:dp[i][j][k]:前 i 个瓶子挑 j 个,它们总容量为 k 时最大的原有水量和。

转移方程:dp[i][j][k] = max { dp[i-1][j][k],dp[i-1][j-1][k-b[i]] + a[i] }

跑完 dp 在 dp[瓶子数][最少瓶子数][0 ~ 瓶子总容量] 中找最大值,再用总水量减掉此最大值,就是最小时间。

或者可以反过来定义:dp[i][j][k]:前 i 个瓶子挑 j 个,这 已有水量和为 k 时的最大瓶子容量和。

状态转移:dp[i][j][k] = max{ dp[i-1][j][k],dp[i-1][j-1][k-a[i]] + b[i] }

然后在 dp[瓶子数][最少瓶子数][0 ~ 总水量] 找答案。

实现时省去第一维,里面两层循环都改成逆序。

初始化为 -1,表示此状态不可达到,也就不能用这个状态转移出其它状态。

Source code

第一种状态定义

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100, V = 100;

struct bottle
{
	int a, b;
	bool operator < (const bottle &bt) const
	{
		return b > bt.b;
	}
} bot[N+1];

int dp[N+1][N*V+1];

int main()
{
	int n, shui = 0, rong = 0;
	scanf("%d", &n);
	for(int i=1; i<=n; ++i)
	{
		scanf("%d", &bot[i].a);
		shui += bot[i].a;
	}
	for(int j=1; j<=n; ++j)
	{
		scanf("%d", &bot[j].b);
		rong += bot[j].b;
	}
	sort(bot + 1, bot + n + 1);
	int ping = 0;
	for(int i=1, w=shui; w>0; ++i)
	{
		w -= bot[i].b;
		++ping;
	}
	memset(dp, -1, sizeof dp);
	dp[0][0] = 0;
	for(int i=1; i<=n; ++i)
		for(int j=ping; j; --j)
			for(int k=rong; k>=bot[i].b; --k)
				if(~dp[j-1][k-bot[i].b]) // 先判是否合法状态
					dp[j][k] = max(dp[j][k], dp[j-1][k-bot[i].b] + bot[i].a);
	int mx = 0;
	for(int i=shui; i<=rong; ++i)
		mx = max(mx, dp[ping][i]);
	printf("%d %d\n", ping, shui-mx);
	return 0;
}

第二种状态定义

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100, V = 100;

struct bottle
{
	int a, b;
	bool operator < (const bottle &bt) const
	{
		return b > bt.b;
	}
} bot[N+1];

int dp[N+1][N*V+1];

int main()
{
	int n, shui = 0;
	scanf("%d", &n);
	for(int i=1; i<=n; ++i)
	{
		scanf("%d", &bot[i].a);
		shui += bot[i].a;
	}
	for(int j=1; j<=n; ++j)
		scanf("%d", &bot[j].b);
	sort(bot + 1, bot + n + 1);
	int ping = 0;
	for(int i=1, w=shui; w>0; ++i)
	{
		w -= bot[i].b;
		++ping;
	}
	memset(dp, -1, sizeof dp);
	dp[0][0] = 0;
	for(int i=1; i<=n; ++i)
		for(int j=ping; j; --j)
			for(int k=shui; k>=bot[i].a; --k)
				if(~dp[j-1][k-bot[i].a])
					dp[j][k] = max(dp[j][k], dp[j-1][k-bot[i].a] + bot[i].b);
	int mx = 0;
	for(int i=shui; ~i; --i)
		if(dp[ping][i] >= shui)
		{
			mx = i;
			break;
		}
	printf("%d %d\n", ping, shui-mx);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值