[关闭]
@xiaoziyao 2020-10-04T11:45:56.000000Z 字数 2979 阅读 982

CF436E Cardboard Box解题报告

解题报告


CF436E Cardboard Box解题报告:

更好的阅读体验

题意

分析

第一眼,很显然可以看出一个的dp做法(设代表前个关卡获得个星星的最小代价),但是这个复杂度无法通过本题。

首先,可以使用一个贪心思想:每一次增加一颗星星,取最小的增加方式

我们考虑一个贪心策略:不断选最小的零星关卡改成一星关卡,或者把一个一星关卡改成二星关卡(贪心地选已经有一星且最小的关卡),但是这个贪心很显然是错误的。

hack:

  1. 2 3
  2. 4 1
  3. 1 9

普通贪心不行,我们可以进行反悔贪心,建议先做一道题了解一下反悔贪心:CF865D Buy Low Sell High

重申一下贪心的思想:每一次增加一颗星星,取最小的增加方式

此时贪心策略有两种:
1. 零星关卡改一星关卡
2. 一星关卡改二星关卡

想一想怎么通过反悔之前的操作来增加一颗星星(即反悔策略):
1. 一星关卡改零星关卡,并将一个零星关卡改成二星关卡;
2. 二星关卡改一星关卡,并将一个零星关卡改成二星关卡。

注意,普通贪心二并不能归入反悔贪心一,因为反悔策略一本质是把一个零星关卡改成二星关卡,而贪心策略二本质是把一个一星关卡改成二星关卡,在维护时会有差别。

现在考虑如何维护这四个策略:
(注意,代码中小根堆是用大根堆权值取负来实现的
1. 贪心策略1:对答案的贡献为,用小根堆来维护零星关卡的
2. 贪心策略2:对答案的贡献为,用小根堆来维护一星关卡的
3. 反悔策略1:对答案的贡献为,我们可以把式子拆成,用小根堆维护零星关卡的最小值,用大根堆维护一星关卡的最大值;
4. 反悔策略2:对答案的贡献为,我们可以把式子拆成,用小根堆维护零星关卡的最小值,用大根堆维护二星关卡的最大值。

还要注意一个细节:有些关卡星值的修改会牵扯到两个堆,此时我们就不能单纯只当前的状态了,还要在每一次取状态时判断状态是否满足,不满足就直接掉。注意这里可以直接的原因是反悔之后的关卡一定不会再次进行反悔。(否则就一定不优了)

时间复杂度:因为每个点最多进行一次贪心选出,一次反悔,加上优先队列,故复杂度为:

代码

需要注意的地方:

  1. #include<stdio.h>
  2. #include<queue>
  3. #define int long long
  4. #define inf 100000000000000000
  5. using namespace std;
  6. const int maxn=300005;
  7. int n,m,anss;
  8. int a[maxn],b[maxn],ans[maxn];
  9. priority_queue< pair<int,int> >q1,q2,q3,q4,q5;
  10. //q1 min{a|a选0}
  11. //q2 max{a|a选1}
  12. //q3 min{b|b选0}
  13. //q4 max{b-a|b选2}
  14. //q5 min{b-a|a选1}
  15. inline void add_0(int x){//加入0
  16. ans[x]=0;
  17. q1.push(make_pair(-a[x],x));
  18. q3.push(make_pair(-b[x],x));
  19. }
  20. inline void add_1(int x){//加入1
  21. ans[x]=1;
  22. q2.push(make_pair(a[x],x));
  23. q5.push(make_pair(-(b[x]-a[x]),x));
  24. }
  25. inline void add_2(int x){//加入2
  26. ans[x]=2;
  27. q4.push(make_pair(b[x]-a[x],x));
  28. }
  29. signed main(){
  30. scanf("%lld%lld",&n,&m);
  31. //优先队列插入初值
  32. q1.push(make_pair(-inf,n+1));
  33. q2.push(make_pair(-inf,n+1));
  34. q3.push(make_pair(-inf,n+1));
  35. q4.push(make_pair(-inf,n+1));
  36. q5.push(make_pair(-inf,n+1));
  37. for(int i=1;i<=n;i++){
  38. scanf("%lld%lld",&a[i],&b[i]);
  39. add_0(i);//初始星值是0
  40. }
  41. for(int k=1;k<=m;k++){//不断增加一个星星
  42. int p1=q1.top().second,p2=q2.top().second,p3=q3.top().second,p4=q4.top().second,p5=q5.top().second;
  43. while(q1.size()>1&&ans[p1]!=0)
  44. q1.pop(),p1=q1.top().second;
  45. while(q2.size()>1&&ans[p2]!=1)
  46. q2.pop(),p2=q2.top().second;
  47. while(q3.size()>1&&ans[p3]!=0)
  48. q3.pop(),p3=q3.top().second;
  49. while(q4.size()>1&&ans[p4]!=2)
  50. q4.pop(),p4=q4.top().second;
  51. while(q5.size()>1&&ans[p5]!=1)
  52. q5.pop(),p5=q5.top().second;
  53. int k1=-q1.top().first,k2=q2.top().first,k3=-q3.top().first,k4=q4.top().first,k5=-q5.top().first;
  54. int t1=k1,t2=k5,t3=k3-k2,t4=k3-k4;
  55. int minn=min(min(t1,t2),min(t3,t4));
  56. //注:这里i指已选的点,j指未选的点
  57. if(t1==minn){
  58. //贪心策略1:选一星(0->a[i])
  59. anss+=t1;
  60. q1.pop();
  61. add_1(p1);
  62. }
  63. else if(t2==minn){
  64. //贪心策略2:一星改成二星(a[i]->b[i])
  65. anss+=t2;
  66. q5.pop();
  67. add_2(p5);
  68. }
  69. else if(t3==minn){
  70. //反悔策略1:一个一星改零星,选另一个二星(a[i]->b[j])
  71. anss+=t3;
  72. q2.pop(),q3.pop();
  73. add_0(p2),add_2(p3);
  74. }
  75. else{
  76. //反悔策略2:一个二星改一星,选另一个二星(b[i]->a[i]+b[j])
  77. anss+=t4;
  78. q3.pop(),q4.pop();
  79. add_1(p4),add_2(p3);
  80. }
  81. }
  82. printf("%lld\n",anss);
  83. for(int i=1;i<=n;i++)
  84. printf("%lld",ans[i]);
  85. puts("");
  86. return 0;
  87. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注