You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8.7 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##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;
}