题目链接:
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 ",ans); 41 return 0; 42 }
结果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 ",ans); 41 return 0; 42 }
但还是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 ",ans); 51 return 0; 52 }
终于AC了!
后来研究发现,用最后一种作法,在三分得出的区间内得出的峰值跟直接三分得到的峰值并不一致。
拓展思考:以后当发现直接三分(二分)查找得出的结果有问题时,可尝试先找出一个区间即可,在这个区间里遍历找出最优解。