01背包;感谢ZCK大佬
题目描述
输入
输出
题面看上去很短,题意也很好理解:就是将n个人分成两组,在人数差不大于1的情况下使它们差最小。
用cnt表示它们的体重之和,则是使两组重量之和尽可能接近cnt / 2。那么就是在cnt / 2的空间里尽可能地使选入的人重。乍一看跟“挤牛奶”没区别,都是两维的dp其中增加一位限制人数。i,j,k三维循环,
for (int i=1; i<=n; i++) for (int j=i; j>=1; j--) for (int k=cnt; k>=a[i]; k--) 因为每个人只能选一次,所以j,k逆序
先来讲讲我最早的想法:
初始化是f[][] = 0
在这种dp[][]方程的定义下,f[i][j]表示1..i人,凑成0..j范围内能凑成的最大重量(我知道这非常奇怪。事实上,在请教ZCK之前我甚至以为这个f[i][j]表示i个人能否凑出j。所以我发现"当f[i][j] != 0时,f[i][j] != j "这个现象时候才开始怀疑我的dp方程)
f[j][k] = max(f[j][k], f[j-1][k-a[i]]+a[i]);
在最终选取ans的时候,由于我们希望ans最接近cnt / 2,那么循环是“cnt / 2 ... 1“的,则ans = max{f[n / 2][]}。在这种情况下,不能够只判断 f[n / 2][i] 。虽然这看上去很有道理——如果n是奇数,用cnt - f[n / 2][i]就是奇数那一队的人数嘛!ZCK解释说:“f[n / 2][] 和 f[n / 2+1][] (n为奇数)是两条不同的数轴,不能保证f[n / 2][]上最接近cnt / 2的值比f[n / 2+1][]上的更优。”由于我们枚举的f[n / 2][] ≤ cnt / 2,那么对应的f[n / 2+1][]就是 ≥ cnt / 2的。有可能n / 2+1人能凑出更接近cnt / 2的!
可能会想到:既然dp时候已经把f[][cnt ... 1]都表示出来了,那么只需要在寻找ans的时候改动一下,变成这样
for (int j=cnt; j>=1; j--) { if (abs(cnt - 2*f[n/2][j]) < abs(cnt-2*ans))ans = f[n/2][j] }
实际上,这样的判断只有70分。
并不是因为寻找ans的过程有问题,是因为f[][]所表示的东西不是我们想要的
这原因是初始化。
memset(f,-127,sizeof(f)); 将f初始化为一个很小很小的数,这样f[i][j]表示的东西就不一样了 f[0][0]=0; 必要的
这样改了之后,只需要"cnt ... 1"判f[n / 2][]即可.
当然"cnt / 2 ... 1"判f[n / 2][], f[n / 2+1][]也可
因为我的dp方程太凌乱了……为了阿掉这题爆交共30+……
ZCK:你的dp方程到底想表示什么???直接bool不就好了吗
bool f[103][45035];
实际上,这的确是最简洁而且dp方程表示最明确的一种方法……
讲了这么多,实际上只是想说明dp方程的 定义 和 初始化 对于dp来说有多么重要。短短的一句初始语句或者在脑海中闪过一瞬的dp方程碎片,就足以上演化爆零为AC的奇迹。一个max和一个if看上去没多大差别,事实上后续的处理可能失之千里。考虑问题要仔细全面,这绝对不是一句空话。