AcWing 1013. 机器分配【分组背包+求字典序最小方案路径】

\(AcWing\) \(1013\). 机器分配

一、题目描述

总公司拥有 \(M\)相同 的高效设备,准备分给下属的 \(N\) 个分公司。

各分公司若获得这些设备,可以为国家提供一定的盈利。盈利与分配的设备数量有关。

问:如何分配这\(M\)台设备才能使国家得到的盈利最大?

求出最大盈利值。

分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数 \(M\)

输入格式

第一行有两个数,第一个数是分公司数 \(N\),第二个数是设备台数 \(M\)

接下来是一个 \(N×M\)的矩阵,矩阵中的第 \(i\) 行第 \(j\) 列的整数表示第 \(i\) 个公司分配 \(j\) 台机器时的盈利。

输出格式

第一行输出最大盈利值;

接下 \(N\)行,每行有 \(2\) 个数,即分公司编号和该分公司获得设备台数。

答案不唯一,输出任意合法方案即可。

数据范围

\(1≤N≤10,1≤M≤15\)

输入样例

3 3
30 40 50
20 30 50
20 25 30

输出样例

70
1 1
2 1
3 1

二、题意理解

样例解读

3 3
30 40 50
20 30 50
20 25 30

\(3\)个公司,\(3\)台机器,机器都是一样的,一样的,记住,一样的,要不题意理解不明白~

  • \(1\)号公司

    • 得到\(1\)台机器,\(30\)
    • 得到\(2\)台机器,\(40\)
    • 得到\(3\)台机器,\(50\)
  • \(2\)号公司

    • 得到\(1\)台机器,\(20\)
    • 得到\(2\)台机器,\(30\)
    • 得到\(3\)台机器,\(50\)
  • \(3\)号公司

    • 得到\(1\)台机器,\(20\)
    • 得到\(2\)台机器,\(25\)
    • 得到\(3\)台机器,\(30\)

问,怎么分,使得国家的收益最大?

:\(1\)号公司得到\(1\)台机器,\(2\)号公司得到\(1\)台机器,\(3\)号公司得到\(1\)台机器,就是\(30+20+20=70\),此时国家利益最大。

二、分组背包

套模型:转换成 分组背包问题 ,做 等价变换


① 第\(i\)个公司就是第\(i\)个分组
② 每个分组中可以一台也不要\(f[i][j]=f[i-1][j]\),如果背包能装的下的前提下:

  • 要一台(类比为第\(1\)个)
  • 要两台(类比为第\(2\)个)
  • ...
  • \(S_i\)台(类比为第\(S_i\)个)

本题特殊的地方就是需要求 最优解时的路径 ,而且可能要的是 字典序最小 的路径。

三、不同\(OJ\)此题的差别

两者差别:

  • \(AcWing\)答案不唯一,输出任意合法方案即可(\(Special\) \(Judge\))

  • 洛谷: \(P.S.\)要求答案的字典序最小

尽管本题是多阶段决策的最小字典序最优方案,但是背包都也类似。
下面是卡最小字典序的数据:

input

2 15
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 2

output

2
1 0
2 15

解决办法

  • \(dfs\)暴搜法
    \(dfs\)保证找到最大答案的时候就是字典序最少的,因为我从\(1 \sim n\)号枚举用的多少机器,用的机器数量也是由少到多。当最后得到答案相等的情况下就不用需要比较字典序了,直接\(return\),只有碰到大小不一的时候才更新答案机器数。

  • \(dp\)+记录路径法

    • 如果\(val > f[i][j]\)转移,打印出来的路径是字典序最大的

    • 如果\(val \geq f[i][j]\)转移,打印出来的路径是字典序最小的

\(Q\):为什么加上等号就是按字典序最小输出呢?

for (int k = 1; k <= j; k++) {
    int val = f[i - 1][j - k] + w[i][k];
    if (val >= f[i][j]) {
        f[i][j] = val;
        path[i][j] = k;
    }
}

: 观察f[i-1][j-k],计算式是\(j-k\),\(k\)前面的符号是负号,也就是\(k\)越小,\(j-k\)越大;\(k\)越大,值越小。按这个逻辑,随着\(k\)长大,就会枚举到更小的\(j-k\)

如果没有等号,是\(k\)越来越大时,\(j-k\)越来越小,当价值一样时,越来越小的个数无法更新结果,反之,如果有等号,就是获取到字典序。

二维数组写法

#include <bits/stdc++.h>

using namespace std;
const int N = 30;

int n, m;
int w[N][N];
int f[N][N];
int path[N][N];

//致敬墨染空大神
void out(int i, int j) {
    if (i == 0) return; //走出界就完事了
    int k = path[i][j];
    out(i - 1, j - k); //利用递推的栈机制,后序输出,太强了~
    printf("%d %d\n", i, k);
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d", &w[i][j]);

    /*1、原始版本*/
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= m; j++) {
            f[i][j] = f[i - 1][j];
            path[i][j] = 0;
            for (int k = 1; k <= j; k++) {
                int val = f[i - 1][j - k] + w[i][k];
                if (val >= f[i][j]) {
                    f[i][j] = val;
                    path[i][j] = k;
                }
            }
        }
    /*2、优化一下代码*/
    /*
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= m; j++) {
            for (int k = 0; k <= j; k++) {
                int val = f[i - 1][j - k] + w[i][k];
                if (val >= f[i][j]) {
                    f[i][j] = val;
                    path[i][j] = k;
                }
            }
        }*/

    //输出最大值
    printf("%d\n", f[n][m]);
    //输出路径
    out(n, m);

    return 0;
}

一维数组写法

#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int f[N];
int w[N];
int path[N][N];

//致敬墨染空大神
void out(int i, int j) {
    if (i == 0) return; //走出界就完事了
    int k = path[i][j];
    out(i - 1, j - k); //利用递推的栈机制,后序输出,太强了~
    printf("%d %d\n", i, k);
}

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++)
            scanf("%d", &w[j]);

        for (int j = m; j; j--)
            for (int k = 1; k <= j; k++) {
                int val = f[j - k] + w[k];
                if (val >= f[j]) {
                    f[j] = val;
                    //在状态转移时,记录路径
                    path[i][j] = k;
                }
            }
    }
    //输出结果
    printf("%d\n", f[m]);
    //输出路径
    out(n, m);
    return 0;
}

找最短路的递归写法

#include <bits/stdc++.h>
using namespace std;

const int N = 20;

int n, m;
int w[N][N];
int f[N][N];
int a[N], al;

void dfs(int u, int v) {
    if (u == 0) return;
    // 寻找当前状态f[i][j]是从上述哪一个f[i-1][k]状态转移过来的
    for (int i = 0; i <= v; i++) {
        if (f[u - 1][v - i] + w[u][i] == f[u][v]) {
            a[++al] = i;
            dfs(u - 1, v - i);
            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);

    int id = 1;
    for (int i = al; i; i--) cout << id++ << " " << a[i] << endl;
    return 0;
}

四、深度优先搜索

数据范围比较小,\(1 \leq N \leq 10,1 \leq M \leq 15\),把\(m\)个机器分配给\(n\)个公司,暴力遍历所有方案

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

#include <bits/stdc++.h>

using namespace std;
const int N = 11;
const int M = 16;
int n;
int m;
int path[N], res[N];
int w[N][M];
int mx;

// u:第几个公司 s:已经产生的价值 r:剩余的机器数量
void dfs(int u, int s, int r) {
    if (u == n + 1) {
        if (s > mx) {
            mx = s;
            memcpy(res, path, sizeof path);
        }
        return;
    }

    for (int i = 0; i <= r; i++) {
        path[u] = i;
        dfs(u + 1, s + w[u][i], r - i); // 给u号公司分配i个机器
        path[u] = 0;
    }
}

int main() {
    cin >> n >> m;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> w[i][j];

    dfs(1, 0, m);

    printf("%d\n", mx);
    // 输出最优答案时的路径
    for (int i = 1; i <= n; i++) printf("%d %d\n", i, res[i]);
    return 0;
}
posted @ 2021-12-21 21:27  糖豆爸爸  阅读(326)  评论(0编辑  收藏  举报
Live2D