1615. [出题活动样题]最优点菜

可以想到,如果 w_{sum}=\sum_{i=1}^mw_i < \sum_{i=1}^na_i ,即全部菜都点还小于全部人的最小食量,那么输出 -1

如果 w_{sum}\ge\sum_{i=1}^na_i ,那么一定是有解的。设 a_{sum}=\sum_{i=1}^na_i,b_{sum}=\sum_{i=1}^nb_i ,设良好的点菜方案总食物量是 \sum d ,则必然有 \sum d\ge a_{sum} ,且 w=\sum d-b_{sum} 。所以只需要枚举所有方案,然后在满足良好的点菜方案里,找到最小的 w 即可。

1\le j\le mj 道菜选与不选对应两个方案,根据乘法原理, m 个选与不选对应共 2^m 个选菜方案。如果直接开循环,对 m 则应该开 m 层循环,每层整型循环变量取 [0,1] ,即可解决问题。如果 m 是恒定的,问题即能解决了。然而 m 是变量, 1\le m\le10 ,如果分类讨论,要列出 10 种冗长的代码,所以考虑更好的解法。

考虑二进制枚举,设一个位数为 m 的二进制数 i ,每个二进制位值都是 01,一个二进制位刚好对应了一个菜选还是不选,那么长为 m 的二进制数就可以确定 m 个菜分别是选还是不选,可以唯一对应一个选菜方案。因此,只需要从 02^m-1 枚举 i ,然后将 i 转成二进制得到每一个位,就可以得知 i 这个选菜方案每道菜选还是不选了。

设从右往左第 j 个二进制位代表第 j 道菜选还是不选, 0 是不选, 1 是选。例如,二进制数 6=(110)_2 可以代表有 3 道菜(因为有 3 个位),选第 2,3 道菜,不选第 1 道菜。又如, 3=(011)_2 代表只选第 1,2 道菜。那么外层循环枚举 [0,2^m-1]i ,内层循环将 i 分解为 m 个二进制位,然后判断第 j 个二进制位,如果是 1 ,那么就让 \sum d加上第 j 道菜的食物量。最后判断 \sum d 是否满足 \sum d\ge a_{sum} ,如果满足,那么求 w_{\min{}}=\min(w_{\min{}},\max(0,\sum d-b_{sum})) 即可。

参考代码如下:

#include <stdio.h> int suma, sumb, n, m, x, d[12], sumd, wmin = 999999999, m2 = 1; int main() { scanf("%d", &n); for (int i = 0; i < n; ++i) { scanf("%d", &x); suma += x; } for (int i = 0; i < n; ++i) { scanf("%d", &x); sumb += x; } scanf("%d", &m); for (int i = 0; i < m; ++i) { scanf("%d", &d[i]); sumd += d[i]; } if (sumd < suma) { printf("-1"); return 0; } for (int i = 0; i < m; ++i) { m2 *= 2; //m2是2的m次方 } for (int i = 0; i < m2; ++i) //枚举每一种选菜方案 { int k = i; int nowd = 0; for (int j = 0; j < m; ++j) { if (k % 2 == 1) //选第j道菜 { nowd += d[j]; } k /= 2; //将二进制的(从右到左)下一位设为最右位 } if (nowd < suma) { continue; } else if (nowd <= sumb) { wmin = 0; } else { int w = nowd - sumb; if (w < wmin) { wmin = w; } } } printf("%lld", wmin); return 0; }