题目:有n个人分成两组,两组人数差不能超过1,找到两组的人重量之差的最小值。
分析:dp,状态压缩01背包。zoj1880升级版。
首先,考虑二维背包。
因为必须放在两个组中的一组,直接背包所有可到状态,取出相差不超过 1的最接近 sum/2的值即可。
然后,优化。
如果直接利用二维背包,由于数据组数较多会TLE,这里利用状态压缩的一维背包;
状态:f(i)表示总重为i时的所有可能的人数,f(i)的值,的第k位上的数是1则代表有k人的组合方式。
转移:f(i)= f(i)| (f(i-w[j])<< 1),等于可以更新到本状态的状态人数集合各加1与原来集合的合。
最后,扫描。
从sum/2开始向下扫描,找到第一个满足题意的解即可。
说明:注意输入0个人的情况。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
long long f[23001];
int h[101];
int gcd(int a, int b)
{
return a%b?gcd(b, a%b):b;
}
int main()
{
int t,n,sum,r;
while (scanf("%d",&t) != EOF)
while (t --) {
scanf("%d",&n);
sum = 0;
for (int i = 1 ; i <= n ; ++ i) {
scanf("%d",&h[i]);
sum += h[i];
}
//荣量优化
r = 1;
if (n > 0) {
r = h[1];
for (int i = 2 ; i <= n ; ++ i)
r = gcd(r, h[i]);
sum /= r;
for (int i = 1 ; i <= n ; ++ i)
h[i] /= r;
}
for (int i = sum/2 ; i >= 0 ; -- i)
f[i] = 0LL;
f[0] = 1LL;
for (int i = 1 ; i <= n ; ++ i)
for (int j = sum/2 ; j >= h[i] ; -- j)
f[j] |= f[j-h[i]]<<1;
int move = sum/2+1;
while (move --) {
if (n%2 == 0 && f[move]&(1LL<<((n+1)/2))) break;
if (n%2 == 1 && f[move]&(1LL<<((n+0)/2))) break;
if (n%2 == 1 && f[move]&(1LL<<((n+1)/2))) break;
}
printf("%d %d\n",move*r,(sum-move)*r);
if (t) printf("\n");
}
return 0;
}