Codeforces 730 J. Bottles DP 0-1背包- 2016-2017 ACM-ICPC, NEERC, Southern Subregional Contest

标签: 解题报告 DP


原题见CF 730

有n个瓶子,各有水量和瓶体积。把水从一个瓶倒到另一个瓶。首先要使得最后不空的瓶子数最少,其次要倒水量最少。求瓶子数和倒水量。

分析

  1. 确定瓶子数。
    对瓶子的体积排序,前km个瓶子体积V恰好不小于总水量之和wt,则km即为最少的瓶子数。
  2. 确定倒水量
    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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值