uva10280

来源:互联网 发布:红磨坊知乎 编辑:程序博客网 时间:2024/06/02 02:23

题目大意:
给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 110int 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;}
0 0
原创粉丝点击