ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

1034 [USACO 2009 Dec G]Video Game Troubles 分组背包变式*

2022-07-12 14:31:41  阅读:177  来源: 互联网

标签:背包 变式 int Troubles 游戏机 ii max fi 2009


题目难度

中等

推荐理由

考验对背包问题的理解

题目知识点

分组背包,0/1背包

题意

农夫约翰有预算 VV,有 NN 台游戏机,每台游戏机价格为 P_iPi​。每台游戏机有G_iGi​个独立游戏,只有买了这台游戏机才能玩对应的游戏,每个游戏价格为 GP_jGPj​,玩了之后奶牛产量增加 PV_jPVj​。问应该买哪些游戏机和游戏,使得奶牛产量最大,求最大产量。
其中,N\leq 50,V\leq100000,P_i\leq1000,G_i\leq10,GP_i\leq100,PV_i\leq 1000000N≤50,V≤100000,Pi​≤1000,Gi​≤10,GPi​≤100,PVi​≤1000000。

解题思路

第一反应:这不是 NOIP2006金明的预算方案 吗?那么这就是一个简单的分组背包问题!!!!
关于分组背包,我这里简要说说。分组背包是这样一种题:
有容量为 mm 的背包,有 nn 个组,每个组有 cnt_icnti​ 个组合,每个组合重量为 w_jwj​,价值为 v_jvj​,每个组只能选一个组合,求最大价值和。
其实每个组是独立的,所以我们每次单独考虑一个组。设 f_ifi​ 为背包容量为 ii 的最大价值和,考虑每个组合选不选,我们有转移方程: f_i=max(f_i,f_{i-w_j}+v_j)fi​=max(fi​,fi−wj​​+vj​)
但是,这样子也许会带来一个问题:如果我们先枚举组合,再枚举容量,无法保证只选取了一个组合。
要保证只选取一个组合怎么办呢?我们先从大到小枚举容量 ii,再枚举组合jj,就可以避免这个问题,因为f_ifi​ 只会被没选过该组组合的f_{i-w_j}fi−wj​​转移而来,这个可能需要读者稍微思考一下。
那么回到这题,我们把每一个游戏机能形成的组合记录下来,每次跑一遍 0/1背包即可。
代码如下:

#include <bits/stdc++.h>
#define N 100005
using namespace std;
int f[N], cnt[55], w[55][1050], v[55][1050], p[11], q[11];
int main(){
    int i, j, k, n, m, a, b, s1, s2;
    scanf("%d%d", &n, &m);
    for(i = 1; i <= n; i++){
        scanf("%d%d", &a, &b);
        for(j = 0; j < b; j++) scanf("%d%d", &p[j], &q[j]);
        for(j = 0; j < (1 << b); j++){
            s1 = s2 = 0;
            for(k = 0; k < b; k++){
                if((1 << k) & j) s1 += p[k], s2 += q[k];
            }
            if(a + s1 > m) continue;
            cnt[i]++;
            w[i][cnt[i]] = a + s1;
            v[i][cnt[i]] = s2;
        }
    }
    for(i = 1; i <= n; i++){
        for(j = m; j >= 0; j--){
            for(k = 1; k <= cnt[i]; k++){
                if(j >= w[i][k]) f[j] = max(f[j], f[j - w[i][k]] + v[i][k]);
            }
        }
    }
    printf("%d", f[m]);
    return 0;
}

然而,光荣TLE了!
这样子的复杂度是 O(2^{G_i}NV)O(2Gi​NV) 的!一看数据范围,当场自闭!
数据范围提示我们,复杂度应该是 O(G_iNV)O(Gi​NV) 的。
那么怎么做呢?如果我们令 f_{i,j}fi,j​ 表示前 ii 个游戏机,花费 jj 元的最大产量。想一想怎么转移。

  • 考虑买第ii个游戏机
    由于游戏机是必买的,先不考虑买游戏,有 f_{i,j}=f_{i-1,j-P_i}fi,j​=fi−1,j−Pi​​。
    接下来考虑买游戏,每个游戏可买可不买,其实就是对第 ii 维做一次 0/1 背包嘛,转移方程:f_{i,j}=max(f_{i,j},f_{i,j-GP_k}+PV_k)fi,j​=max(fi,j​,fi,j−GPk​​+PVk​)。注意这里是对第 ii 维做0/1背包,和第 i-1i−1 维无关。
  • 考虑不买第 ii 个游戏机
    这个很简单,f_{i,j}=max(f_{i,j},f_{i-1,j})fi,j​=max(fi,j​,fi−1,j​)就行了。

来到这里这题就做完了。
不过还有一个要注意的地方,在实现的时候这两部分是有顺序的,也就是必须先考虑买游戏机,再考虑不买游戏机。这个是为什么呢?其实是因为我们偷懒,直接把 ff 的第 ii 维拿来做 0/1 背包了。。如果用 g_{j}gj​ 单独表示第 ii 维用了 jj 元的最大产量,就不用管顺序了。

分析

很容易想到金明的预算方案。但是看题解说时间复杂度不适合。然后把金明预算方案的分组背包优化成了01背包。

先算的是选择这个背包,然后算不选择这个背包,所有背包数量那一维度要留着,还是选择问题。过段时间得复习这道题

 

代码如下

#include <bits/stdc++.h>
#define N 100005
using namespace std;
int f[55][N];
int main(){
    int i, j, k, n, m, a, b, w, v;
    scanf("%d%d", &n, &m);
    for(i = 1; i <= n; i++){
        scanf("%d%d", &a, &b);
        for(j = m; j >= a; j--) f[i][j] = f[i - 1][j - a];//必须买游戏机 
        for(j = 1; j <= b; j++){
            scanf("%d%d", &w, &v);
            for(k = m; k >= w + a; k--){//01背包,注意 k 是枚举到 w + a 
                f[i][k] = max(f[i][k], f[i][k - w] + v);
            }
        }
        for(j = 0; j <= m; j++) f[i][j] = max(f[i][j], f[i - 1][j]);//不买游戏机 
    }
    printf("%d", f[n][m]);
    return 0;
}

标签:背包,变式,int,Troubles,游戏机,ii,max,fi,2009
来源: https://www.cnblogs.com/er007/p/16469949.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有