The Knapsack problem Gym

来源:互联网 发布:slf4j 输出sql 编辑:程序博客网 时间:2024/06/05 15:12


D - The Knapsack problem

 Gym - 101064L

To prepare for the ICPC World Finals 2017, your team attended to a training camp. Glauber "the drinker" Sokolov was the teacher, and all teams had to answer a form so he would know what topics were the best to teach. The first day was introductory and Glauber said he would just go over well-known topics. He said "I noticed you all answered that you knew the Knapsack problem very well, so here is a simple exercise about it, follows directly from the definitions. Solve the knapsack problem with repetitions.".

More formally, given N types of items, each one with a weight and a cost, choose some items such that the sum of its weights does not exceed S, and the sum of costs is maximum. You may choose any number of items of each type.

Solve Glauber's exercise.

Input

On the first line two integers N and S, the number of types of items and the maximum allowed weight.

Each of the next N lines has two integers wi and ci, the weight and the cost of the ith type of item.

Limits

  • 1 ≤ N, wi ≤ 103
  • 0 ≤ S, ci ≤ 109
Output

Print a single integer, the cost of the items you choose.

Example
Input
3 10014 513 25 1
Output
35

题意:给定背包可容纳总重量Sn种物品,每种物品有自己的重量w和价值c,每种物品能使用多次,问背包能装物品的最大价值是多少。

这题是队内训练的时候做的,当时一看,这不是裸的完全背包么,大水题,快切,然而写完了才注意到数据范围。。一下就傻X了。后来队友说可以把包分成两部分,较大的部分贪心,剩下的再做dp,因为贪心放性价比最高的东西的话,只要是正好放满,性价比一定是最高的, 当时觉得这个思路很对,结果一开始dp部分留下的太小,wa4,然后给dp部分留了1e6,结果T63,一直到训练结束也没调出来。。赛后看了别人的代码才发现,尽管贪心部分是最优的,dp部分单独来看也是最优的(但dp部分可能有剩余空间),但是两部分加起来不一定是最优的,不管dp部分留多大,只要不是S,就会有这种问题,因此应该枚举dp部分的大小,剩下的贪心去做,然后发现一个问题就是不知道枚举的上限是多少。。看别人的代码的枚举上限千奇百怪,有max_w*1000的,也有max_w乘以性价比最高的那个的重量的, 感觉他们找的上限都毫无道理啊,就找不出有说服力的证据说明这个上限是对的, 但是人家就是能过,这就很气了。

之后的一天,我看了某学长给找的官方题解,研究了好长时间才大彻大悟,简直是醍醐灌顶,也更加坚定了我认为原来看的那种做法基本上可以算是蒙过去的想法。先把官方题解贴上:




 

 

题解已经把正确方法介绍的很详细了,就是有几个关键点还需要自己认真思考一下,如果自行理解透了,也有助于理解背包类问题的本质思想,不过个人感觉题解有个地方应该是写错了:f(S)=max(f(x)+f(S-x)).(1<=x<S)这里的f就是我们通常写的dp数组。

我说一下个人的理解吧:

注意我以下提到的max_w上限为1000,官方题解中的w上限是500,切忌搞混。

我们要求容量为S的背包,然而因为S太大而无法一次性求出,我们就把S分成两个较小的背包,然而大背包的最优解并不简单的等于两个小背包最优解的加和(理同上面红字部分,如果不明白,建议再去好好地重新学一遍基础的背包问题),我们需要枚举两个背包的容量各是多少,这里引用题解中的表述,假设一个小背包容量为x,则另一个为S-x,然后就到了最关键的一步|x-S-x))|<=1000,即两背包容量绝对值之差小于等于最大物品重量,在这个范围内必定有fx+fS-x)等于fS)的最优解,这个点的理解非常重要,蒟蒻的表达能力有限,就说个大概吧:因为单独做两个小背包都有可能有未填满部分,将这两个其中一个背包的未填满部分匀给另一个的话就有可能得到一个更优的解,然而匀出去的这部分不会超过物品的最大重量,因为(匀出去的部分中)超过最大重量的部分放到两个背包中的任意一个都是可以得到最优解的,所以那部分匀和不匀是一样的效果,而我们要尽可能减小枚举的区间,所以只要枚举S/2-max_w/2<=x<=S/2+max_w/2就好了。

然而S/2还是非常大,但有了上面的结论,我们自然可以继续同样的分下去,将S/2-max_w/2<=x<=S/2+max_w/2区间内的每一个x按照上面的方法分成两个小背包,然后就会产生一个新的区间,新产生的这些区间有很大部分是重复的,最后总的区间就只有

S/4-max_w*3/4<=x<=S/4+max_w*3/4这么大,可以证明,当S一直被分到S/2^k的时候,最后产生的总区间一定是

S/2^k-max_wS/2^k+max_w)的子集。max_w前面的系数不会超过一。1/23/47/815/16.....

因此最终我们只需要做S/2^k-max_wS/2^k+max_w大小的背包,然后再一层一层的递推回去就好了,而这递推过程其实又是另一种dp求最优解的过程。

更多细节详见代码:

#include<bits/stdc++.h>#define ll long long#define pi acos(-1)#define inf 0x3f3f3f3f#define lson l,mid,rt<<1#define rson mid+1,r,rt<<1|1#define rep(i,x,n) for(int i=x;i<n;i++)#define per(i,n,x) for(int i=n;i>=x;i--)using namespace std;typedef pair<int,int>P;const int MAXN=1010;int gcd(int a,int b){return b?gcd(b,a%b):a;}int w[1010],v[1010];int len[33];ll dp[5030],f[33][3030];int main(){int n,s,maxw=0;cin>>n>>s;rep(i,0,n)cin>>w[i]>>v[i],maxw=max(maxw,w[i]);int cnt=0;while(s>=1){len[cnt++]=s;//保存的是递推过程中每一个总区间的左边界s=(s-maxw)>>1;}for(int i=0;i<n;i++)for(int j=w[i];j<=maxw*5;j++)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);for(int i=cnt-1;i>=0;i--)//逐层递推回去,求得总背包的最优解{for(int j=len[i];j<=len[i]+2*maxw;j++){if(j<=5*maxw)f[i][j-len[i]]=dp[j];else{for(int k=(j-maxw)>>1;k+k<=j;k++)f[i][j-len[i]]=max(f[i][j-len[i]],f[i+1][k-len[i+1]]+f[i+1][j-k-len[i+1]]);} }}cout<<f[0][0]; return 0;}

然后是莫名其妙水过的代码:

#include<cstdio>#include<iostream>#include<algorithm>using namespace std;typedef long long ll;ll kj=0;ll dp[1000005];ll v[1005],w[1005];int main (void){ll n,W;scanf("%lld %lld", &n, &W);for(int i=1;i<=n;i++)scanf("%lld %lld", &w[i], &v[i]);int maxi=1;for(int i=1; i<=n; ++i) {if(v[i]*w[maxi]>v[maxi]*w[i]) maxi = i;}kj=w[maxi]*1000;kj=min(W,kj);for(int i=1;i<=n;i++) {for(int j=w[i];j<=kj;j++) {dp[j]=max(dp[j],dp[j-w[i]]+v[i]);}}ll ans=0;for(int i=0;i<=kj;i++){ll temp=dp[i]+(W-i)/w[maxi]*v[maxi];ans=max(temp,ans);}printf("%lld\n", ans);}


dp真的是博大精深,想要完全掌握绝非一日之功所能及啊。


0 0
原创粉丝点击