HDU 5445 Food Problem

题意:有n种糖果,m种箱子,需要的能量为p。每种糖果有它的能量、体积、数量,每种箱子有它的容量、价格、数量,问至少获得p能量的前提下,最少花多少钱


思路:首先可以知道这是一个背包,然后可以求出能量为i的时候最小的体积。如果箱子也这么做的话会超时,因为求出的最小的体积会很大,所以背包空间太大。题目中给出最终的金额不超过50000,所以可以求出当前金额能获得的最大体积,这样扫一遍就知道结果了。


多重背包:这里使用了单调队列优化多重背包,优化掉了二进制多重背包的log。

单调队列优化背包:详见:《浅谈几类背包题》


然后说下自己的理解:F[j*v+d]-j*w,对于放进单调队列中的这个key值我开始很不理解-j*w是个什么东西,后来理解了发现很巧妙。

对于每个j,先把现在j对应的体积(j*v+d)放进单调队列,然后再更新dp[j*v+d],实际上就是用i-1的结果来更新i。

对于-j*w,假设当前单调队列在j的时候处理完毕,队列首的元素为j',所以就要用j'的key值更新j,j'和j之间的值其实就是相差(j-j')*w,也就是j*w-j'*w,这两项就是论文中加上和减去的那两项。

//唉,弱逼看了好久才懂T T


代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int inf = 0x3f3f3f3f;
struct Node {
    int val,cost,num;
}na[210];
int dp[50500];
struct QQ{
    int j,key;
}qq[510000];
void mul_bag_min(int n,int C){
    memset(dp,inf,sizeof(dp));
    dp[0] = 0;
    for(int i = 0;i < n;i ++){
        int val = na[i].val,cost = na[i].cost,num = na[i].num;
        for(int d = 0;d < val;d ++){
            int head = 0,tail = -1;
            int siz = (C-d)/val;
            for(int j = 0;j <= siz;j ++){
                int vv = j*val+d;
                int key = dp[vv] - j*cost;
                while(tail >= head&&qq[tail].key >= key)tail --;
                qq[++tail] = (QQ){j,key};
                while(tail >= head&&qq[head].j + num < j)head ++;
                dp[vv] = min(dp[vv],qq[head].key + j*cost);
                //cout<<vv<<' '<<dp[vv]<<endl;
            }
        }
    }
}
void mul_bag_max(int n,int C){
    memset(dp,0,sizeof(dp));
    for(int i = 0;i < n;i ++){
        int val = na[i].val,cost = na[i].cost,num = na[i].num;
        for(int d = 0;d < val;d ++){
            int head = 0,tail = -1;
            int siz = (C-d)/val;
            for(int j = 0;j <= siz;j ++){
                int vv = j*val+d;
                int key = dp[vv] - j*cost;
                while(tail >= head&&qq[tail].key <= key)tail --;
                qq[++tail] = (QQ){j,key};
                while(tail >= head&&qq[head].j + num < j)head ++;
                dp[vv] = max(dp[vv],qq[head].key + j*cost);
                //cout<<vv<<' '<<dp[vv]<<endl;
            }
        }
    }
}
int main(){
    int T;
    scanf("%d",&T);
    while(T --){
        int n,m,p;
        scanf("%d%d%d",&n,&m,&p);
        for(int i = 0;i < n;i ++){
            scanf("%d%d%d",&na[i].val,&na[i].cost,&na[i].num);
        }
        mul_bag_min(n,p+150);
        int mina = inf;
        for(int i = p;i < p+150;i ++){
            mina = min(mina,dp[i]);
        }
        if(mina == inf){
            puts("TAT");
            continue;
        }
        for(int i = 0;i < m;i ++){
            scanf("%d%d%d",&na[i].cost,&na[i].val,&na[i].num);
        }
        mul_bag_max(m,50000);
        int ans = inf;
        for(int i = 0;i <= 50000;i ++){
            if(dp[i] >= mina){
                ans = i;
                break;
            }
        }
        if(ans > 50000)puts("TAT");
        else cout<<ans<<endl;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值