Bzoj 1229: [USACO2008 Nov]toy 玩具 题解 三分+贪心
1229: [USACO2008 Nov]toy 玩具
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 338 Solved: 136
[Submit][Status][Discuss]
Description
玩具 [Chen Hu, 2006] Bessie的生日快到了, 她希望用D (1 <= D <= 100,000; 70%的测试数据都满足 1 <= D <= 500)天来庆祝. 奶牛们的注意力不会太集中, 因此Bessie想通过提供玩具的方式来使它们高兴. 她已经计算出了第i天需要的玩具数T_i (1 <= T_i <= 50). Bessie的幼儿园提供了许多服务给它们的奶牛程序员们, 包括一个每天以Tc (1 <= Tc <= 60)美元卖出商品的玩具店. Bessie想尽可能的节省钱, 但是Farmer John担心没有经过消毒的玩具会带来传染病(玩具店卖出的玩具是经过消毒的). 有两种消毒的方式. 第1种方式需要收费C1美元, 需要N1个晚上的时间; 第2种方式需要收费 C2美元, 需要N2个晚上的时间(1 <= N1 <= D; 1 <= N2 <= D; 1 <= C1 <= 60; 1 <= C2 <= 60). Bessie在party结束之后把她的玩具带去消毒. 如果消毒只需要一天, 那么第二天就可以拿到; 如果还需要一天, 那么第三天才可以拿到. 作为一个受过教育的奶牛, Bessie已经了解到节约的意义. 帮助她找到提供玩具的最便宜的方法.
Input
* 第 1 行: 六个用空格隔开的整数 D, N1, N2, C1, C2, Tc
* 第 2..D+1 行: 第 i+1 行包含一个整数: T_i
Output
第 1 行: 提供玩具所需要的最小费用.
Sample Input
8
2
1
6
输入解释:
Bessie想开4天的party, 第1天需要8个玩具, 第2天需要2个玩具, 第3天需要1个玩具,
第4天需要6个玩具. 第一种方式需要$2, 用时1天; 第二种方式需要$1, 用时2天. 买
一个玩具需要$3.
Sample Output
输出解释:
第 1 天 买8个玩具, 花去$24; 送2个玩具去快洗, 6个慢洗.
第 2 天 取回2个快洗的玩具, 花去$4. 送1个玩具去慢洗.
第 3 天 取回6个慢洗的玩具, 花去$6.
第 4 天 取回所有的玩具(与现有的加在一起正好6个), 花去$1. 这样就用了最少的钱.
1 #include <iostream> 2 #include <cstdlib> 3 #include <cstdio> 4 #include <cstring> 5 #include <queue> 6 #include <algorithm> 7 #include <cmath> 8 #include <map> 9 #include <set> 10 #define N 100005 11 using namespace std; 12 int n,va1,va2,t1,t2,bu,t[N]; 13 int sum[5],ans,st[N],top; 14 int main() 15 { 16 freopen("toy.in","r",stdin); 17 freopen("toy.out","w",stdout); 18 scanf("%d%d%d%d%d%d",&n,&t1,&t2,&va1,&va2,&bu); 19 for(int i=1;i<=n;i++) 20 { 21 scanf("%d",&t[i]); 22 } 23 if(va1>va2)swap(va1,va2),swap(t1,t2); 24 //cout<<endl; 25 int now=0; 26 for(int i=1;i<=n;i++) 27 { 28 if(i>t1) sum[1]+=t[i-t1]; 29 if(i>t2) 30 { 31 sum[2]+=t[i-t2]; 32 top++; 33 st[top]=i-t2; 34 } 35 if(bu<=va1&&bu<=va2) 36 { 37 ans+=bu*t[i]; 38 continue; 39 } 40 if(sum[1]>=t[i]) 41 { 42 sum[1]-=t[i]; 43 ans+=va1*t[i]; 44 if(t1>=t2)sum[2]-=t[i]; 45 } 46 else 47 { 48 ans+=va1*sum[1]; 49 if(t1>=t2) 50 { 51 sum[2]-=sum[1]; 52 if(bu<=va2) 53 { 54 ans+=bu*(t[i]-sum[1]); 55 } 56 else 57 { 58 if(sum[2]>=t[i]-sum[1]) 59 { 60 ans+=va2*(t[i]-sum[1]); 61 sum[2]-=(t[i]-sum[1]); 62 int js=0; 63 while(top) 64 { 65 if(js+t[st[top]]>=t[i]-sum[1]) 66 { 67 t[st[top]]-=t[i]-sum[1]-js; 68 if(!t[st[top]])top--; 69 break; 70 } 71 js+=t[st[top]]; 72 t[st[top]]=0; 73 top--; 74 } 75 } 76 else 77 { 78 ans+=va2*sum[2]; 79 while(top) 80 { 81 t[st[top]]=0; 82 top--; 83 } 84 ans+=bu*(t[i]-sum[1]-sum[2]); 85 sum[2]=0; 86 } 87 } 88 } 89 else 90 { 91 ans+=bu*(t[i]-sum[1]); 92 } 93 sum[1]=0; 94 } 95 //cout<<ans<<endl; 96 } 97 printf("%d\n",ans); 98 return 0; 99 }
为什么这么打不对呢?假设我们快洗和买相差无几,但是慢洗要便宜很多,那么我们在较早的时间快洗会导致之后的慢洗无法使用现在的玩具,导致虽然我们在当时省了一点小钱,但最终我们丢了大钱(好有哲理啊)。
那么我们到底该怎么办呢?
我们上面这种打法之所以不对,是因为我们把原本应该买新玩具的地方使用了快洗,那么我们为什么不能通过确定我们总共要买多少玩具来避免呢?当然可以,但对于买的玩具的个数我们枚举肯定是不行的,那么二分可以吗?用十二指肠想一想也知道不行,那么我们能否用三分呢?好像是可以的,假设我们免费买,那么我们买的越多花的钱越少,但实际还是要花钱去买的,所以貌似就是一个三分。之前在cgh_Andy的题解中发现了证明,大家想看可以去看一下(据说还有拿导数证的%%%)。
那么我们既然知道了三分的值,我们就可以按照类似与我之前的方法进行贪心,唯一不一样的是这次我们能买就买。别的还是照常,维护一个双向队列就好了。
1 #include <iostream> 2 #include <cstdlib> 3 #include <cstdio> 4 #include <cstring> 5 #include <queue> 6 #include <algorithm> 7 #include <cmath> 8 #include <map> 9 #include <set> 10 #define N 100005 11 using namespace std; 12 int n,va1,va2,t1,t2,bu,t[N],a[N]; 13 int sum,mx,q[N],hea,en; 14 int check(int x) 15 { 16 hea=1,en=0; 17 memset(q,0,sizeof(q)); 18 memcpy(a,t,sizeof(t)); 19 int ans=x*bu; 20 for(int i=1;i<=n;i++) 21 { 22 if(i>t2) 23 { 24 en++; 25 q[en]=i-t2; 26 } 27 if(t[i]<=x) 28 { 29 x-=t[i]; 30 continue; 31 } 32 for(int j=t[i]-x;j;) 33 { 34 if(en<hea)return 0x7fffffff; 35 if(q[hea]<=i-t1) 36 { 37 int k=min(a[q[hea]],j); 38 a[q[hea]]-=k,j-=k; 39 ans+=va1*k; 40 if(!a[q[hea]])hea++; 41 } 42 else 43 { 44 int k=min(a[q[en]],j); 45 a[q[en]]-=k,j-=k; 46 ans+=va2*k; 47 if(!a[q[en]])en--; 48 } 49 } 50 if(x)x=0; 51 } 52 return ans; 53 } 54 int main() 55 { 56 scanf("%d%d%d%d%d%d",&n,&t1,&t2,&va1,&va2,&bu); 57 for(int i=1;i<=n;i++) 58 { 59 scanf("%d",&t[i]); 60 sum+=t[i]; 61 mx=max(mx,t[i]); 62 } 63 if(va1>va2)swap(va1,va2),swap(t1,t2); 64 int li=mx,ri=sum; 65 while(li<ri-10) 66 { 67 int lmid=(li*2+ri)/3,rmid=(li+ri*2)/3; 68 int ans1=check(lmid),ans2=check(rmid); 69 if(ans1<ans2)ri=rmid; 70 else li=lmid; 71 } 72 73 int ans=0x7fffffff; 74 for(int i=li;i<=ri;i++) 75 { 76 ans=min(check(i),ans); 77 } 78 printf("%d\n",ans); 79 return 0; 80 }
Ps:一开始交上去还T了,因为打错了三分,我是不是该找一些三分的题练一下,因为我貌似还没有一次自己打过三分成功的说。