HDU 4091 Zombie’s Treasure Chest (每日一题)

来源:互联网 发布:淘宝质量问题怎么解决 编辑:程序博客网 时间:2024/05/20 21:22

大意是: 一些战士来到了一个村庄,发现了两种宝石和一个宝箱,每种宝石的个数是无限的,给出了宝箱的大小 N 和两种宝石的 size 和 value ,即 N s1 v1 s2 v2 ,已知这五个两,求利用这个宝箱,战士能够带走多大价值的宝石。 五个数的范围是 int 。 也就是 10^9。

如果范围比较小的话,就直接可以用多重背包的解法。 但是数据范围是 10^9 就没法用 多重背包来做了。。

在这给出一种数学的解法,非背包的解法。

解: 1 。求出 s1 ,s2 的最小公倍数。最小公倍数等于s1乘以s2 在除以s1和s2在最大公约数即lcm=(s1*s2)/gcd(s1,s2)。

                   为什么求最小公倍数呢?

                   会有这么一个性质存在。。a = lcm /s1 , b = lcm/s2 。 最小公倍数除以s1 s2 后的到 a,b  。 a 个宝石一 和 b 个宝石二 所占的空间是相同的。

                   于是对于最优解中,单位价值小的宝石取得个数应该小于最大公倍数除以这个宝石体积  。也就是说 如果宝石一的单位价值小的话, 那么num1 < a。

                   证明。设num1>a,那么对于这num1 个宝石 从中取出a个 ,把这a 个换成b个宝石二。由于a个宝石一 跟b个宝石二的所占的体积相同,但宝石二的单位价值更大。所以更换以后的总价值会更大,则num1 的个数应该小于a。

          2 。 因此我们可以把N 划分成两部分来求 1。int  vn =  N%lcm +2*lcm 。(其实N%lcm +lcm 就够了但不会证明就用了N%lcm+2*lcm了。最效率影响不大)  2。N - vn  这两部分。 对部分二 一定是lcm 的倍数 直接能够求出这部分的最优解。关键是部分一。 为什么划分出部分1呢?  我们可以单独考虑宝石一小于a 的部分和宝石二小于b的部分。vn 定能覆盖这两部分的体积。

对于vn 这暴力求解。   for from 0 to vn/(max(s1,s2))   循环大的值,是因为循环次数少,提高效率。这个循环最坏情况大致是10^4 到10^5  。

两部分总价值的就是解。。

(表达不好。。。。。。。。)

 

附上本人的代码:(注 代码不够优雅,正在努力改正)

 

#include <iostream>#include <cstdio>#include <cstring>#define LL long longusing namespace std;int t;int n,s1,v1,s2,v2;// 求最大公约数int gcd(int da ,int xiao){       int temp =0;        while(xiao!=0){            temp = da%xiao;            da =xiao;            xiao = temp;        }        return da;}int max(int a,int b){ return  (a>b)?a:b;}// LL 在 define  是long longLL max2(LL a,LL b){    return (a>b)?a:b;}int min(int a,int b){  return (a<b)?a:b;}LL cal (){    LL sum = n;    //answer 为 最大价值  最后返回这个answer    LL answer = 0;    //a 为 s1 s2 中的大值,va 为 a 的value        int  a = max(s1,s2);        int  b = min(s1,s2);        int  va,vb;     if(s1==a){        va = v1;        vb = v2;     }else{        va = v2;        vb = v1;     }     // 求最小公倍数  。。 返回值 longlong 否则 wa 我一开始就wa 在这       LL lcm = s2/gcd(a,b)*(LL)s1;       if(sum>=2*lcm){           int num = (sum-2*lcm)/lcm;           answer += num*max2(lcm/s1*v1,lcm/s2*v2);           sum =sum - num*lcm;       }       int  i_max = sum/a;       LL maxn=0;       //循环暴力求解       for(int i=0 ;i<=i_max;i++){            int j = (sum-i*a)/b;            LL tmp = i*(LL)va+j*(LL)vb;            maxn=max2(maxn,tmp);       }       answer += maxn;       return  answer;}int main(){     scanf("%d",&t);     for(int i=1;i<=t;i++){         scanf("%d%d%d%d%d",&n,&s1,&v1,&s2,&v2);         printf("Case #%d: ",i);         cout << cal()<<endl;     }}


 

这个题目一定要注意  long long 的范围 。。。。。

这个题目某些情况可以特判后提高效率  。比如正好整除的情况 。。。   

 

此题还有其他解法,稍后附上。

                        

          

      

 


 

原创粉丝点击