题目大意:
给L升酒,有n个酒瓶,每个酒瓶有个最大容量和最小容量,酒瓶的个数不限。
问最多剩多少毫升酒没有装进去。
思路:
完全背包+剪枝
只要问题中含有数量不限且问最多最少问题的都属于完全背包问题。
剪枝 limit=min{min*min/(max-min)}。
对于同一种的瓶子,如果我们用k个瓶子去装酒的话,那么可以装下的酒的范围是[k * min,k max],随着k不断的变大,左边区间的间距都是一样的,但是区间的长度不断的在变大,意思就是说右边的区间的间距越拉越大。那么当k大到一定程度的时候,k + 1的左区间 < k的右区间,接下去每个区间就都会有重叠的部分,即k max >=(k + 1)min,解得 k >= min /(max - min),说明只要酒量x >= k min,那么所有的酒就可以全部装进去,否则必定会有留下的。化简得 x >= min*min/(max - min)
最后说一说dp的过程吧,有了上面的剪枝,我们最起码能把总酒量的状态减到4500*4500,大约是2*10^7,剩下的工作就是把瓶子按不同的容积拆成max-min+1个瓶子,然后做完全背包的dp。这样我们不妨想一下,最后瓶子数乘上酒量的状态数会不会超呢?实际上由于min<=0.99max,所以即使容量是4500也实际只有不到450000个状态,远小于2*10^7,当然最大是否有可能超过450000而且最大是多少还要根据剪枝的函数来确切算一下,而且我们dp前是可以把体积重复的瓶子去掉的,由于判重的剪枝瓶子数最后能不能到比较大的水平也是个问题,所以整体的复杂度受多方因素的制约。
就剪枝min*min/(max-min)来讲,由于min*min/(max-min)<=min*min/(min/0.99-min)<450000,所以最多不会超过450000个状态。
代码:
#include <iostream>
using namespace std;
#include <stdio.h>
#include <cstring>
#define MAXN 110
int L,N,_min[MAXN],_max[MAXN],f[450000],vis[4600],v[18000];
void DP() {
scanf("%d %d",&L,&N);
L = L * 1000;
int limit = 0x3f3f3f3f;
for(int i = 0; i < N;i++) {
scanf("%d %d",&_min[i],&_max[i]);
if(_min[i] * _min[i]/(_max[i] - _min[i]) < limit)
limit = _min[i] * _min[i] /(_max[i] - _min[i]);
}
if(L > limit) {
printf("0\n");
return;
}
int n = 0;
memset(vis,0,sizeof(vis));
memset(v,0,sizeof(v));
for(int i = 0; i < N; i++)
for(int j = _min[i]; j <= _max[i]; j++)
if(!vis[j]) { //去重
vis[j] = 1;
v[n ++] = j;
}
memset(f,0,sizeof(f));
for(int i = 0; i < n; i++)
for(int j = v[i]; j <= L; j++) {
if(f[j - v[i]] + v[i] > f[j])
f[j]= f[j - v[i]] + v[i];
}
printf("%d\n", L - f[L]);
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
DP();
if(T)
printf("\n");
}
return 0;
}


2299

被折叠的 条评论
为什么被折叠?



