E - Lucky bag

发布时间 2023-12-11 17:33:24作者: onlyblues

E - Lucky bag

Problem Statement

AtCoder Inc. sells merchandise on its online shop.

There are $N$ items remaining in the company. The weight of the $i$-th item $(1\leq i\leq N)$ is $W_i$.

Takahashi will sell these items as $D$ lucky bags.
He wants to minimize the variance of the total weights of the items in the lucky bags.
Here, the variance is defined as $V=\frac{1}{D}\displaystyle\sum_{i=1}^D (x_i-\bar{x})^2$, where $x_1,x_2,\ldots,x_D$ are the total weights of the items in the lucky bags, and $\bar{x}=\frac{1}{D}(x_1+x_2+\cdots+x_D)$ is the average of $x_1,x_2,\ldots,x_D$.

Find the variance of the total weights of the items in the lucky bags when the items are divided to minimize this value.
It is acceptable to have empty lucky bags (in which case the total weight of the items in that bag is defined as $0$),
but each item must be in exactly one of the $D$ lucky bags.

Constraints

  • $2 \leq D\leq N\leq 15$
  • $1 \leq W_i\leq 10^8$
  • All input values are integers.

Input

The input is given from Standard Input in the following format:

$N$ $D$
$W_1$ $W_2$ $\ldots$ $W_N$

Output

Print the variance of the total weights of the items in the lucky bags when the items are divided to minimize this value.
Your output will be considered correct if the absolute or relative error from the true value is at most $10^{-6}$.


Sample Input 1

5 3
3 5 3 6 3

Sample Output 1

0.888888888888889

If you put the first and third items in the first lucky bag, the second and fifth items in the second lucky bag, and the fourth item in the third lucky bag, the total weight of the items in the bags are $6$, $8$, and $6$, respectively.

Then, the average weight is $\frac{1}{3}(6+8+6)=\frac{20}{3}$,
and the variance is $\frac{1}{3}\left\{\left(6-\frac{20}{3}\right)^2+\left(8-\frac{20}{3}\right)^2+\left(6-\frac{20}{3}\right)^2 \right\}=\frac{8}{9}=0.888888\ldots$, which is the minimum.

Note that multiple items may have the same weight, and that each item must be in one of the lucky bags.

 

解题思路

  为了方便这里用 $m$ 来表示题目中的 $D$,且物品和分组的下标都从 $0$ 开始。

  容易知道 $\bar{x} = \frac{1}{m}\sum\limits_{i=0}^{m-1}{x_i} = \frac{1}{m}\sum\limits_{i=0}^{n-1}{w_i}$,因此 $\bar{x}$ 是一个常量。所以要使得式子 $\frac{1}{m}\sum\limits_{i=0}^{m-1}{(x_i - \bar{x})^2}$ 尽可能小,只需让 $\sum\limits_{i=0}^{m-1}{(x_i - \bar{x})^2}$ 尽可能小。因此实际上要考虑的是每组应该放哪些物品才能使得式子尽可能小。

  考虑动态规划,定义 $f(i,j)$ 表示已将状态 $j$ 所表示的物品放入前 $i$ 组的所有方案中 $\sum\limits_{k=0}^{i}{(x_k - \bar{x})^2}$ 的最小值,其中 $j$ 是一个 $n$ 位的二进制数,如果第 $k$ 位是 $1$ 表示已选第 $k$ 个物品。为了方便这里定义 $s_j$ 表示状态 $j$ 中已选物品的总价值,根据第 $i$ 组含有哪些物品进行状态划分,因此有状态转移方程 $$\begin{cases} f(i,j) = (s_j - \bar{x})^2 ,&i=0 \\ f(i,j) = \min\limits_{k \subset j}\left\{ f(i-1,j \oplus k) + (s_k - \bar{x})^2 \right\} ,&i>0 \end{cases}$$

  其中 $k$ 也是二进制状态,表示 $j$ 的二进制状态的子集。例如 $(101)_2$,那么其子集就是 $(101)_2$,$(100)_2$,$(001)_2$,$(000)_2$。用 $\operatorname{popcount}(j)$ 表示二进制数 $j$ 中 $1$ 的数量,那么 $j$ 的子集数量就是 $2^{\operatorname{popcount}(j)}$。遍历 $j$ 的子集的方法和证明请参考链接

  整个 dp 的时间复杂度是 $O\left( m \cdot \sum\limits_{i=0}^{n}{2^i \cdot C_{n}^{i}}  \right) = O(m \cdot 3^n)$。其中 $\sum\limits_{i=0}^{n}{2^i \cdot C_{n}^{i}}$ 可由二项式定理 $(1 + 2)^n$ 推出。计算量上限大概是 $2 \times 10^8$ 级别,在 atcoder 上 $300 \; ms$ 左右就可以跑出结果。

  最终答案就是 $\frac{1}{m} \cdot f(m-1, \, 2^n-1)$。

  AC 代码如下:

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

typedef long long LL;

const int N = 15, M = 1 << 15;

int w[N], s[M];
double f[N][M];

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%d", w + i);
    }
    for (int i = 0; i < 1 << n; i++) {
        for (int j = 0; j < n; j++) {
            if (i >> j & 1) s[i] += w[j];
        }
    }
    double avg = 1.0 * s[(1 << n) - 1] / m;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < 1 << n; j++) {
            if (!i) {
                f[i][j] = (s[j] - avg) * (s[j] - avg);
            }
            else {
                f[i][j] = f[i - 1][j] + avg * avg;
                for (int k = j; k; k = (k - 1) & j) {
                    f[i][j] = min(f[i][j], f[i - 1][j ^ k] + (s[k] - avg) * (s[k] - avg));
                }
            }
        }
    }
    printf("%.10f", f[m - 1][(1 << n) - 1] / m);
    
    return 0;
}

 

参考资料

  Editorial - AtCoder Beginner Contest 332:https://atcoder.jp/contests/abc332/editorial/7929