uva10280

题目大意:
给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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值