CF730J Bottles
CF730J Bottles
题目描述
Nick has n n bottles of soda left after his birthday. Each bottle is described by two values: remaining amount of soda \(a_{i}\) and bottle volume \(b_{i} ( a_{i}<=b_{i})\)。
Nick has decided to pour all remaining soda into minimal number of bottles, moreover he has to do it as soon as possible. Nick spends \(x\) seconds to pour \(x\) units of soda from one bottle to another.
Nick asks you to help him to determine \(k\) — the minimal number of bottles to store all remaining soda and \(t\) — the minimal time to pour soda into \(k\) bottles. A bottle can't store more soda than its volume. All remaining soda should be saved.
输入格式
The first line contains positive integer \(n ( 1<=n<=100)\) — the number of bottles.
The second line contains \(n\) positive integers \(a_{1},a_{2},...,a_{n}( 1<=a_{i}<=100)\), where \(a_{i}\) is the amount of soda remaining in the \(i\) -th bottle.
The third line contains \(n\) positive integers \(b_{1},b_{2},...,b_{n}( 1<=b_{i}<=100)\) , where \(b_{i}\) is the volume of the \(i\) -th bottle.
It is guaranteed that \(a_{i}<=b_{i}\) for any \(i\) .
输出格式
The only line should contain two integers \(k\) and \(t\) , where \(k\) is the minimal number of bottles that can store all the soda and \(t\) is the minimal time to pour the soda into \(k\) bottles.
题意翻译
题目描述
有 \(n\) 瓶水,第 \(i\) 瓶水的水量为 \(a_i\),容量为 \(b_i\) 。
将 \(1\) 单位水从一个瓶子转移到另一个瓶子所消耗时间为 \(1\) 秒,且可以进行无限次转移。求储存所有水所需最小瓶子数 \(k\) 以及该情况下所用最小时间 \(t\)。
输入格式
第一行输入一个正整数 \(n(1\le n\le 100)\)。
第二行输入 \(n\) 个正整数,第 \(i\) 个正整数表示 \(a_i (1\le a_i \le 100)\)。
第三行输入 \(n\) 个正整数,第 \(i\) 个正整数表示 \(b_i (1\le b_i \le100)\)。
对于每一个 \(i\),满足 \(a_i\le b_i\)。
输出格式
输出一行两个整数:\(k\) 和 \(t\)。
输入输出样例
输入 #1
4
3 3 4 3
4 7 6 5
输出 #1
2 6
输入 #2
2
1 1
100 100
输出 #2
1 1
输入 #3
5
10 30 5 6 24
10 41 7 8 24
输出 #3
3 11
说明/提示
In the first example Nick can pour soda from the first bottle to the second bottle. It will take \(3\) seconds. After it the second bottle will contain \(3+3=6\) units of soda. Then he can pour soda from the fourth bottle to the second bottle and to the third bottle: one unit to the second and two units to the third. It will take \(1+2=3\) seconds. So, all the soda will be in two bottles and he will spend \(3+3=6\) seconds to do it.
Solution
这是一道非常好的背包 \(dp\) 题目,关键在于化未知于已知,进而转化题目进一步求解。
首先对于第一问求最小瓶子数 \(k\),这还是很好求的。
一个显然的贪心策略:优先选容量大的瓶子,直到装满了再换下一个瓶子。
我们所要装的水的体积 \(S_a=a_1+a_2+...+a_n\) 是确定的,所以我们先按照瓶子的容量 \(b_i\) 从大到小排序,然后依次用瓶子去装即可。
n=read();
for(int i=1;i<=n;i++)
{
a[i].a=read();
Sa+=a[i].a; //所有水的体积和
}
for(int i=1;i<=n;i++)
{
a[i].b=read();
Sb+=a[i].b; //所有瓶子的容量和
}
sort(a+1,a+1+n,cmp); //按照瓶子的容量从大到小排序
int S=Sa; //S表示还剩多少水没被装
for(int i=1;i<=n;i++) //依次用每个瓶子去装
{
S-=a[i].b;
if(S<=0) //如果用完第i个瓶子后水被装完了,说明最少用i个
{
printf("%d ",i);
m=i; //m记录最少要用的瓶子数
break;
}
}
第二问求最小时间 \(t\),这个就有点费劲了。
我们分析一下什么时候会消耗时间:即将水从一个瓶子里移动到另一个瓶子中。由于题目中说到:移动 \(1\) 单位的水需要 \(1s\),也就是说,消耗的时间就是我们移动的水的体积。
我们想让移动的水尽量少,换句话说,我们想让不动的水尽量多。
什么时候水不动呢?假如我们最少用了 \(k\) 个瓶子去装所有的水,那么这 \(k\) 个瓶子中的水是不是不用动?我们只需要让别的瓶子里的水移动到这 \(k\) 个瓶子中去。
所以说,不动的水是我们选的这 \(k\) 个瓶子中的水,我们要让它尽量大。
所以我们将题目进一步转化,做法就很显然了:
我们需要在 \(n\) 个瓶子中选出 \(k\) 个,使得这 \(k\) 个瓶子的容积和 \(>=S_a(\)要装下所有的水\()\),问这 \(k\) 个瓶子对应的 \(a_i\) 的和最大是多少。
这样就变成了一个裸的背包问题:
状态设置
\(dp[i][j]\) 表示选了 \(i\) 个瓶子,容积和为 \(j\) 的情况下,原有的水的最大体积是多少。
状态转移
这里我用的是刷表法。
第一层 \(i\) 枚举所有的瓶子,第二层 \(j\) 枚举容积和,第三层 \(k\) 枚举选了几个瓶子,然后考虑第 \(i+1\) 个瓶子选不选。
如果选:\(dp[k+1][j+b_{i+1}]=\max\{dp[k][j]+a_{i+1}\}\)。
如果不选,则不转移。
边界条件
起初把所有的 \(dp\) 都设为 \(-∞\),\(dp[0][0]=0\)。
答案输出
我们 \(dp\) 求出的是不动的水的最大体积,要求最少时间还需要用水的总体积 \(S_a\) 减去不动的水的最大体积。
Code
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
inline int read()
{
char ch=getchar();
int a=0,x=1;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') a=a*10+(ch^48),ch=getchar();
return a*x;
}
const int N=105;
int n,m,Sa,Sb;
int dp[N][N*N];
struct node
{
int a,b;
}a[N];
bool cmp(node x,node y)
{
return x.b>y.b;
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
a[i].a=read();
Sa+=a[i].a; //所有水的体积和
}
for(int i=1;i<=n;i++)
{
a[i].b=read();
Sb+=a[i].b; //所有瓶子的容量和
}
sort(a+1,a+1+n,cmp); //按照瓶子的容量从大到小排序
int S=Sa; //S表示还剩多少水没被装
for(int i=1;i<=n;i++) //依次用每个瓶子去装
{
S-=a[i].b;
if(S<=0) //如果用完第i个瓶子后水被装完了,说明最少用i个
{
printf("%d ",i);
m=i; //m记录最少要用的瓶子数
break;
}
}
memset(dp,-0x3f,sizeof(dp)); //要求最大值,dp要初始化为负无穷大
dp[0][0]=0; //边界条件
for(int i=0;i<=n;i++) //由于用的是刷表法,所以i要从0开始
{
int A=a[i+1].a;
int B=a[i+1].b;
for(int j=Sb;j>=0;j--)
{
for(int k=0;k<=m;k++)
{
dp[k+1][j+B]=max(dp[k+1][j+B],dp[k][j]+A); //选与不选取max
}
}
}
int ans=0;
for(int i=Sa;i<=Sb;i++) ans=max(ans,dp[m][i]); //注意第二维最小是Sa,因为要保证这m个瓶子能装下所有的水,所以容积和要>=Sa
printf("%d\n",Sa-ans); //最小时间(最小移动体积)=总体积-最大的不动的体积
return 0;
}