CF808E Selling Souvenirs

题目链接:

http://codeforces.com/contest/808/problem/E

题目大意:

  Petya 有 n 个纪念品,他能带的最大的重量为 m,各个纪念品的重量为 wi,花费为 ci,问 Petya 能带的纪念品的最大价值几何?

心得:

  刚开始以为是01背包,开开心心地写了个dp上去超时ORZ。后来想要用记忆化,发现开不出这么大的数组,所以想了很久也想不出个所以然。

  后来经一位大神一篇博文的点拨(链接:http://www.cnblogs.com/wmrv587/p/6876314.html),决定用三分法试试。

  于是看了一篇介绍三分查找的博文(链接:http://blog.csdn.net/pi9nc/article/details/9666627)

  然后动手写了第一版代码:

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <functional>
 4 using namespace std;
 5 const int MAXN=100000+5;
 6 int cost[5][MAXN];
 7 int t[5];
 8 int n,m;
 9 long long sum[5][MAXN];
10 int main()
11 {
12     scanf("%d%d",&n,&m);
13     for(int i=1;i<=n;i++){
14         int k;
15         scanf("%d",&k);
16         scanf("%d",&cost[k][++t[k]]);
17     }
18     for(int i=1;i<=3;i++)   sort(cost[i]+1,cost[i]+t[i]+1,greater<int>());
19 
20     for(int i=1;i<=3;i++)
21         for(int j=1;j<=t[i];j++)
22             sum[i][j]=sum[i][j-1]+cost[i][j];
23     long long ans=0;
24     for(int i=0;i<=m;i++){
25         int l=0,r=min(t[2],i/2);                      //以2重量的来计算
26         while(l<r){
27             int mid=(l+r)/2,mmid=(mid+r)/2;
28             int t11=min(i-mid*2,t[1]),t12=min(i-mmid*2,t[1]);
29             if(sum[2][mid]+sum[1][t11]>sum[2][mmid]+sum[1][t12])
30                 r=mmid;
31             else
32                 l=mid+1;
33         }
34         int t1=min(i-l*2,t[1]);
35         long long temp=sum[2][l]+sum[1][t1];
36         int t3=min(t[3],(m-i)/3);
37         temp+=sum[3][t3];
38         ans=max(ans,temp);
39     }
40     printf("%I64d\n",ans);
41     return 0;
42 }
View Code

  结果Wrong answer on test 8

  在那里debug了2个小时,把它改成了这样:

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <functional>
 4 using namespace std;
 5 const int MAXN=100000+5;
 6 int cost[5][MAXN];
 7 int t[5];
 8 int n,m;
 9 long long sum[5][MAXN];
10 int main()
11 {
12     scanf("%d%d",&n,&m);
13     for(int i=1;i<=n;i++){
14         int k;
15         scanf("%d",&k);
16         scanf("%d",&cost[k][++t[k]]);
17     }
18     for(int i=1;i<=3;i++)   sort(cost[i]+1,cost[i]+t[i]+1,greater<int>());
19 
20     for(int i=1;i<=3;i++)
21         for(int j=1;j<=t[i];j++)
22             sum[i][j]=sum[i][j-1]+cost[i][j];
23 
24     long long ans=0;
25     for(int i=0;i<=m;i++){
26          int l=0,r=min(t[2],i/2);                      //以2重量的来计算
27        for(int k=0;k<100;k++){
28             int mid=(l+r)/2,mmid=(mid+r)/2;
29             if(sum[2][mid]+sum[1][i-mid*2]>=sum[2][mmid]+sum[1][i-mmid*2])
30                 r=mmid;
31             else
32                 l=mid;
33         }
34         int t1=min(i-l*2,t[1]);
35         long long temp=sum[2][l]+sum[1][t1];
36         int t3=min(t[3],(m-l*2-t1)/3);
37         temp+=sum[3][t3];
38         ans=max(ans,temp);
39     }
40     printf("%I64d\n",ans);
41     return 0;
42 }
View Code

  但还是WA。

  于是去参考排行榜上很靠前的一位选手的做法,发现其中一位的做法跟我很相似,但是他在 l-r<=30 的时候就停止了三分查找,然后再遍历 [l,r] 这个区间,找出最优解。

  仿照这个做法,我写了第三个版本:

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <functional>
 4 using namespace std;
 5 const int MAXN=100000+5;
 6 int cost[5][MAXN];
 7 int t[5];
 8 int n,m;
 9 long long sum[5][MAXN];
10 int main()
11 {
12     scanf("%d%d",&n,&m);
13     for(int i=1;i<=n;i++){
14         int k;
15         scanf("%d",&k);
16         scanf("%d",&cost[k][++t[k]]);
17     }
18     for(int i=1;i<=3;i++)   sort(cost[i]+1,cost[i]+t[i]+1,greater<int>());
19 
20     for(int i=1;i<=3;i++)
21         for(int j=1;j<=t[i];j++)
22             sum[i][j]=sum[i][j-1]+cost[i][j];
23 
24     long long ans=0;
25     for(int i=0;i<=m;i++){
26         int l=0,r=min(t[2],i/2);
27         while(r-l>30){
28             int mid=(l+r)/2,mmid=(mid+r)/2;
29             if(sum[2][mid]+sum[1][i-mid*2]>=sum[2][mmid]+sum[1][i-mmid*2])
30                 r=mmid;
31             else
32                 l=mid;
33         }
34         int t1=min(t[1],i-2*l);
35         long long maxc=sum[2][l]+sum[1][t1];
36         int maxn=l,maxm=t1+l*2;
37         for(int j=l+1;j<=r;j++){
38             int tt1=min(t[1],i-2*j);
39             if(sum[2][j]+sum[1][tt1]>maxc){
40                 maxn=j;
41                 maxc=sum[2][j]+sum[1][tt1];
42                 maxm=j*2+tt1;
43             }
44         }
45         long long temp=maxc;
46         int t3=min(t[3],(m-maxm)/3);
47         temp+=sum[3][t3];
48         ans=max(ans,temp);
49     }
50     printf("%I64d\n",ans);
51     return 0;
52 }
View Code

  终于AC了!

  后来研究发现,用最后一种作法,在三分得出的区间内得出的峰值跟直接三分得到的峰值并不一致。

  

  拓展思考:以后当发现直接三分(二分)查找得出的结果有问题时,可尝试先找出一个区间即可,在这个区间里遍历找出最优解。

  

  

  

转载于:https://www.cnblogs.com/Blogggggg/p/6898716.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值