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

来源:互联网 发布:mac日历清理 编辑:程序博客网 时间:2024/05/05 21:48

标签: 解题报告 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 10010struct 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 0
原创粉丝点击