标签: 解题报告 DP
原题见CF 730
有n个瓶子,各有水量和瓶体积。把水从一个瓶倒到另一个瓶。首先要使得最后不空的瓶子数最少,其次要倒水量最少。求瓶子数和倒水量。
分析
- 确定瓶子数。
对瓶子的体积排序,前km个瓶子体积V恰好不小于总水量之和wt,则km即为最少的瓶子数。 - 确定倒水量
dp[i][j][k]
表示前i个瓶子选取k个(且第i个为所选第k个),使得k个瓶子体积和为j,可以容纳的最大水量。
先求出在dp[n-1][wt到V][km]
的max
,再用wt-max
即答案。
由于轮换,可降维到dp[j][k]
。
另外通过reach[j][k]
表示是否可到达该状态。
j
,k
的两层循环位置可调换,答案不变。但是一种比另一种速度快一倍,这个问题组原课有解释。
由于是0-1背包,须注意j
,k
是循环递减来遍历,否则就是完全背包了。
代码
/*--------------------------------------------
* File Name: CF 730J
* Author: Danliwoo
* Mail: Danliwoo@outlook.com
* Created Time: 2016-10-24 00:46:49
--------------------------------------------*/
#include <bits/stdc++.h>
using namespace std;
#define N 110
#define M 10010
struct node
{
int w, v;
void pr() {
printf("[%d , %d]\n", w, v);
}
}p[N];
bool cmp(node a, node b){
return a.v > b.v;
}
int dp[M][N], n;
bool reach[M][N];
void gao(int km, int wt, int V) {
memset(dp, 0, sizeof(dp));
memset(reach, false, sizeof(reach));
reach[0][0] = true;
for(int i = 0;i < n;i++) {
int v = p[i].v, w = p[i].w;
for(int j = V;j >= v;j--)
for(int k = min(km, i+1);k >= 1;k--) if(reach[j-v][k-1]){
reach[j][k] = true;
dp[j][k] = max(dp[j][k], dp[j-v][k-1] + w);
}
}
int ans = 0;
for(int j = wt;j <= V;j++)
ans = max(ans, dp[j][km]);
ans = wt - ans;
printf("%d %d\n", km, ans);
}
int main(){
while(~scanf("%d", &n)) {
int wt = 0;
for(int i = 0;i < n;i++) {
scanf("%d", &p[i].w);
wt += p[i].w;
}
for(int i = 0;i < n;i++)
scanf("%d", &p[i].v);
sort(p, p+n, cmp);
int km = 0, vt = 0;
for(int i = 0;i < n;i++) {
vt += p[i].v;
if(vt >= wt) {
km = i+1;
break;
}
}
gao(km, wt, vt);
}
return 0;
}