zoukankan      html  css  js  c++  java
  • AcWing 1013. 机器分配【分组背包+求方案数】

    题目传送门

    一、深度优先搜索

    \(m\)个机器分配给\(n\)个公司,暴力遍历所有方案

    记录分配方案,如果能更新最优解,顺便更新一下最优解的分配方案

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 11; //最多N个公司
    const int M = 16; //最多M个设备
    int n;            //n个公司
    int m;            //m个设备
    int path[N];      //记录每一次尝试时的路径记录数组,但不一定是最优的,在达到最优时复制到path里
    int w[N][M];      //第i个公司拿j个机器时获取到的价值
    
    int Max;          //价值的最大值
    int res[N];       //最佳的取法数组,一直记录到目前为止最优的方案
    
    /**
     * 功能:深度优先搜索求最大值及方案
     * @param step     第x个公司
     * @param sum     总价值
     * @param r     剩余设备数量
     */
    void dfs(int step, int sum, int r) {
        //如果走完了所有公司,就可以回头统计结果了
        if (step == n + 1) {
            //更新最大值
            if (sum > Max) {
                Max = sum;
                //复制保留当前最优路径
                memcpy(res, path, sizeof path);
            }
            return;
        }
        //从0到r(剩余机器数量),分别尝试分给第x个公司
        for (int i = 0; i <= r; i++) {
            path[step] = i;       //给第x个公司i个设备
            //开始尝试x+1个公司,此时价值增加了w[x][i],剩余机器数量减少了i
            dfs(step + 1, sum + w[step][i], r - i);
        }
    }
    
    int main() {
        cin >> n >> m;
        //读入第i个公司,拿j个设备时的价值是多少
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                cin >> w[i][j];
        //爆搜
        dfs(1, 0, m);//站在第1个公司面前,爆搜,目前的总价值是0,剩余的机器数量是m
        //输出最大价值
        printf("%d\n", Max);
        //输出最大价值时的选择方法
        for (int i = 1; i <= n; i++)
            printf("%d %d\n", i, res[i]);
        return 0;
    }
    

    二、分组背包

    本题乍一看很像是 背包DP,为了转换成 背包DP问题,我们需要对里面的一些叙述做出 等价变换

    每家公司 我们可以看一个 物品组,又因为 每家公司 最终能够被分配的 机器数量 是固定的

    因此对于分给第 \(i\)公司 的不同 机器数量 可以分别看作是一个 物品组 内的 物品

    物品 \(k\) 的含义:分给第 \(i\) 个 公司 \(k\) 台机器

    物品 \(k\) 的体积:\(k\)

    物品 \(k\) 的价值:\(w_{ik}\)

    于是,本题就转换成了一个 分组背包问题

    直接上 分组背包闫氏DP分析法

    初始状态 :\(f[0][0]\)

    目标状态 :\(f[N][M]\)

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 30;
    
    int n, m;       //n个公司,m个机器
    int w[N][N];    //第i个公司,拿j个机器时可以得到的价值
    int f[N][N];    //dp结果,第一维:前i个公司,第二维:在j个机器的情况下的最优解
    int path[N];     //最优解的路径
    
    int main() {
        //读入
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                cin >> w[i][j];//读入第i个公司,拿j个机器时能获取到的价值
    
        //二维分组背包模板
        for (int i = 1; i <= n; i++)        // 枚举组
            for (int j = 0; j <= m; j++) {  // 枚举体积
                f[i][j] = f[i - 1][j];      // 一个都不选的情况
                for (int k = 1; k <= j; k++)// 枚举物品,数量和价值在此统一到一起表示
                    f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
            }
        //输出最大值
        printf("%d\n", f[n][m]);
    
        //求方案路径的套路
        int j = m;  //j代表空间大小
        for (int i = n; i >= 1; i--)        //倒序遍历每组,注意求解方案时需要从下向上
            for (int k = 0; k <= j; k++)    //看看f[i][j]是从哪个前序状态转化而来
                if (f[i][j] == f[i - 1][j - k] + w[i][k]) {
                    path[i] = k;//记录下来
                    j -= k;    //体积减小
                    break;     //找到一组即可
                }
        //输出路径
        for (int i = 1; i <= n; i++) printf("%d %d\n", i, path[i]);
        return 0;
    }
    

    三、动态规划求状态转移路径

    这里我介绍一个从 图论 角度思考的方法

    动态规划 本质是在一个 拓扑图 内找最短路

    我们可以把每个 状态\(f[i][j]\)看作一个 状态的转移 看作一条 ,把 状态的值 理解为 最短路径长

    具体如下图所示:

    对于 点 \(f[i][j]\) 来说,他的 最短路径长 是通过所有到他的 更新出来的

    更新 最短路规则 因题而已,本题的 更新规则\(f(i,j)=max\{f(i−1,j−v_i)+w_i\}\)
    最终,我们会把从 初始状态(起点)到 目标状态 (终点)的 最短路径长 更新出来

    随着这个更新的过程,也就在整个 中生成了一颗 最短路径树

    该 **最短路径树 **上 起点终点路径 就是我们要求的 动态规划的状态转移路径

    如下图所示:

    那么 **动态规划求状态转移路径** 就变成了在 **拓扑图** 中找 **最短路径 **的问题了

    可以直接沿用 最短路 输出路径的方法就可以找出 状态的转移

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 20;
    
    int n, m;
    int w[N][N];
    int f[N][N];
    int path[N], cnt;
    
    //i,j这个状态是从哪个状态转化而来,记录一下路径
    void dfs(int i, int j) {
        if (i == 0) return;//递归出口
        //寻找当前状态f[i][j]是从上述哪一个f[i-1][k]状态转移过来的
        for (int k = 0; k <= j; k++) {  //遍历每个可能的转移体积
            if (f[i - 1][j - k] + w[i][k] == f[i][j]) {
                path[cnt++] = k;//记录从哪里来~
                dfs(i - 1, j - k);//沿着这条路线继续吧
                return;//找到一个合理解就不再探讨其它的体积
            }
        }
    }
    
    int main() {
        //input
        cin >> n >> m;
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j)
                cin >> w[i][j];
    
        //dp
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j)
                for (int k = 0; k <= j; ++k)
                    f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
        cout << f[n][m] << endl;
    
        //find path
        dfs(n, m);
    
        for (int i = cnt - 1, id = 1; i >= 0; i--, id++)
            cout << id << " " << path[i] << endl;
        return 0;
    }
    
    
  • 相关阅读:
    JavaScript系列:《JavaScript高级程序设计》,chapter2, 在html中使用JavaScript
    Java系列:JVM指令详解(下)(zz)
    Java系列:JVM指令详解(上)(zz)
    Java系列:关于Java中的桥接方法
    REST: C#调用REST API (zz)
    Activiti系列:为什么Activiti 5.18 的REST的api总是返回404错误
    timeSeries db之:使用Metrics监控应用程序的性能 (zz)
    Java系列:国际化(zz)
    通过数据库方式访问excel 2007及其以后(xlsx)文件的连接字符串
    java系列:《java核心技术 卷1》学习笔记,chapter 11 调试技巧
  • 原文地址:https://www.cnblogs.com/littlehb/p/15717260.html
Copyright © 2011-2022 走看看