题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1171
题目大意
有 n 种物品,每种物品有一个大小和数量。要求将所有的物品分成两部分,使两部分的总大小尽量接近。
题目分析
多重背包果题。
令 sum 为所有物品的大小总和。那么就是用给定的物品做多重背包,背包容量为sum/2,得到的结果是较小的一部分的大小。然后多重背包问题可以使用单调队列优化,O(nm) 。
这是我的多重背包+单调队列第一题。纪念一下。
对了,顺便说一下优化方法【以下引自kxy课件原话】
关于多重背包的优化
从简单的出发:
dp[ i ][ j ]=到第 i 个物品,选了重量不超过 j 的最大价值
dp[ i ][ j ]=max { dp[ i-1 ][ j-k*w[ i ] ]+k*v[ i ] } –条件略
我们可以观察到,根据 j mod w[ i ] 的值不同,可以分为w[i] 组,( 每一组的情况都是一样的 )
这是显然的,不用证明我们先考虑 j mod w[ i ] =0 的情况
定义:
a[ j ]=dp[ i ][ j * w[ i ] ]
那么 dp[ i ][ (j+k)*w[ i ] ]=max { a[ j ]+k*v[ i ] , a[ j+1 ]+(k-1)*v[ i ] , …… , a[ j+k ] }
似乎发现了什么??定义 b[ j ]=a[ j ]-j*v[ i ]
那么 dp[ i ][ (j+k)*w[ i ] ]=max { b[ j ] , b[ j+1 ] , …… , b[ j+k ] }+( j+k )*v[ i ]dp[ i ][ (j+k)*w[ i ] ]=max { b[ j ] , b[ j+1 ] , …… , b[ j+k ] }+( j+k )*v[ i ]
求b[ j~j+k ]的最大值,用队列优化就行了
引用完毕,orz
下面贴代码(模板,对了kxy模板i从0到n-1是过不了n=1的情况的,i必须做到n,实践证明)
DIE 马
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 1010
#define M 300000
using namespace std;
int n, m, sum;
int dp[M], q[M], qv[M], num[N], w[N];//dp数组滚动使用,q[]双端队列,下标, qv[]双端队列,保存值
int head, tail;
int main(){
while(~ scanf("%d", &n) && n >= 0){
sum = 0;
memset(q, 0, sizeof(q));//记得清空
memset(qv, 0, sizeof(qv));
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++){
scanf("%d%d", &w[i], &num[i]);
sum += w[i] * num[i];
}
m = sum / 2;
for(int i = 0; i <= n; i++){
for(int a = 0; a < w[i]; a++){
head = 0; tail = -1;
for(int j = 0; j * w[i] + a <= m; j++){
int val = dp[j * w[i] + a] - j * w[i];
while(head <= tail && qv[tail] <= val) tail --;
q[++tail] = j; qv[tail] = val;//加入j
dp[j * w[i] + a] = qv[head] + j * w[i];
if(q[head] == j - num[i]) head ++;//退掉无用的值
}
}
}
printf("%d %d\n", sum - dp[m], dp[m]);
}
return 0;
}
我和谁都不争,
和谁争我都不屑;
我爱大自然,
其次就是艺术;
我双手烤着,
生命之火取暖;
火萎了,
我也准备走了.
——[英]兰德(杨绛 译)