可以想到,如果 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 m 第 j 道菜选与不选对应两个方案,根据乘法原理, m 个选与不选对应共 2^m 个选菜方案。如果直接开循环,对 m 则应该开 m 层循环,每层整型循环变量取 [0,1] ,即可解决问题。如果 m 是恒定的,问题即能解决了。然而 m 是变量, 1\le m\le10 ,如果分类讨论,要列出 10 种冗长的代码,所以考虑更好的解法。
考虑二进制枚举,设一个位数为 m 的二进制数 i ,每个二进制位值都是 0 或 1,一个二进制位刚好对应了一个菜选还是不选,那么长为 m 的二进制数就可以确定 m 个菜分别是选还是不选,可以唯一对应一个选菜方案。因此,只需要从 0 到 2^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;
}