7 ACwing 277 cookies 题解

cookies

题面

圣诞老人共有 M 个饼干,准备全部分给 N 个孩子。

每个孩子有一个贪婪度,第 i 个孩子的贪婪度为 g[i]。

如果有 a[i] 个孩子拿到的饼干数比第 i 个孩子多,那么第 i 个孩子会产生 g[i]×a[i] 的怨气。

给定 N、M 和序列 g,圣诞老人请你帮他安排一种分配方式,使得每个孩子至少分到一块饼干,并且所有孩子的怨气总和最小。

\(1 \le N \le 30\)

\(N \le M \le 5000\)

题解

这道题的阶段比较明显,就是“分了几个人”,“分了多少饼干”,所以状态很容易设,但是并不是很好转移,因为每个人的贡献要根据其他人分得的饼干数确定,所以我们需要确定一个分饼干的顺序,方便我们计算每个人的贡献

根据“邻项交换法”容易证明,贪婪度大的孩子拿到更多的饼干是更优的,所以我们将孩子按照贪婪度从大到小排序,这样他们分得的饼干数也是从大到小的,非严格单减

但如果有两个人的饼干数相同,那么后面那个人的贡献就不是 \(g_i \times (i - 1)\) ,而是 \(g_i \times (i - 2)\)

所以我们需要维护有多少个人是相同的,但这个东西在我们 dp 数组中并不好维护,所以我们采用另一种方法,想想正整数拆分的过程,我们将 \(j\) 个饼干分给 \(i\) 个人,其实也就相当于把 \(j\) 这个数拆分成 \(i\) 个正整数

我们可以利用类似正整数拆分的方法,每次要么整体 +1,要么新插入一个 1,这样我们在插入最后一个人的时候,可以枚举前面有几个 1 ,也就是这些人都拥有相同的饼干数

为什么这样可行?因为在考虑一个人的贡献的时候,我们只考虑有多少个人比他多,也就是他们之间的相对关系,我们并不关心每个人具体有多少,所以整体 +1 的操作并不会影响我们的答案

那么设 \(f(i,j)\) 表示前 \(i\) 个人分了 \(j\) 个饼干的最小贡献,转移为

\[f(i,j) = min \{f(i, j - i), f(i - k, j - k) + (i - k) \times \sum_{p = i - k + 1}^i g_p \} \\ \]

k$ 相当于我们枚举的前面 \(1\) 的个数,\(f(i,j) = f(i,j-i)\) 相当于整体 +1

那个 \(\sum\) 可以用前缀和 \(O(1)\) 算,所以总的时间复杂度为 \(O(N^2M)\)

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;

const int N = 50, M = 5010;

int n, m;
ll f[N][M], sum[N];
int ans[N];

struct node {
    int g, id;
} a[N];

bool cmp (node a, node b) {
    return a.g > b.g;
}

int main () {
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i].g;
        a[i].id = i;
    }
    sort (a + 1, a + 1 + n, cmp);

    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i - 1] + a[i].g;
    }

    memset (f, 0x3f, sizeof f);

    f[0][0] = 0;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (j >= i) f[i][j] = f[i][j - i];
            for (int k = 1; k <= i && k <= j; k ++) {
                f[i][j] = min (f[i][j], f[i - k][j - k] + (i - k) * (sum[i] - sum[i - k]));
            }
        }
    }


    cout << f[n][m] << endl;

    {
        //h 表示前面 +1 的次数
        int i = n, j = m, h = 0;
        while (i && j) {
            if (j >= i && f[i][j] == f[i][j - i]) {
                j -= i;
                h ++;
            } else {
                for (int k = 1; k <= i && k <= j; k ++) { 
                    if (f[i][j] == f[i - k][j - k] + (i - k) * (sum[i] - sum[i - k])) {
                        for (int u = i; u > (i - k); u--) {
                            ans[a[u].id] = 1 + h;
                        }
                        i -= k;
                        j -= k;
                        break;
                    }
                }
            }
        }
    }

    for (int i = 1; i <= n; i ++) {
        cout << ans[i] << ' ';
    }

    return 0;
}
posted @ 2025-10-05 17:59  michaele  阅读(22)  评论(0)    收藏  举报