USACO 2017 December Contest Platinum T3: Greedy Gift Takers
题目大意
有 N(1≤N≤1e5)头牛按顺序排成一列,编号从 1 到 N,1 号牛在队头,N 号牛在队尾。
每次位于队头的牛 i 拿到一个礼物,然后插入到从队尾数ci头牛之前的位置。。举个栗子: 初始队列 1,2,3,4,5 c1= 2,c2 = 3,则第一次操作后的序列为 2,3,1,4,5,第二次操作后的序列为 3,2,1,4,5。重复无限次操作,求最后有几头牛拿不到礼物。
题目分析
一上来有个显然的结论,若一个人得不到礼物那么原序列中在他后面的人肯定也得不到礼物,因为后面的人跳不到前面来。
所以我们只要找到第一个拿不到礼物的人,计算一下就是答案。
也就是说,我们要找到最后一个能拿到礼物的人。
显然,这个具有单调性,可以二分。
二分可以拿到礼物的人数,那么怎么判断可不可行呢?
我们要判断在 mid之前 是否形成了一个循环即可。若形成了循环,则mid不能拿到礼物。
怎么判断循环呢,出现在前i个位置的牛有多于i头,则这i头牛就会一直卡在这前i个位置。
注意,Check时要把 c[1] ~ c[mid-1] 排序,因为c大的拿礼物一次后可能还会跳到mid前面,而c小的就永远留在后面了,这样相当于在 “c较大的牛“ 的c上减了1。
而让c小的全跳到后面去而不构成循环,才是我们真正要判断的。
(不理解的可以对着代码仔细想一下)
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int MAXN=1e5+10; 4 5 int n,ans; 6 int c[MAXN],num[MAXN]; 7 inline bool Check(int mid){ 8 if(mid==1) return true; 9 for(int i=1;i<mid;++i) num[i]=c[i]; 10 sort(num+1,num+mid); 11 int lim=n-mid; 12 for(int i=1;i<mid;++i){ 13 if(num[i]>lim) return false; 14 ++lim; 15 } 16 return true; 17 } 18 int main(){ 19 scanf("%d",&n); 20 for(int i=1;i<=n;++i) 21 scanf("%d",&c[i]); 22 int l=1,r=n+1; 23 while(l<=r){ 24 int mid=(l+r)>>1; 25 if(Check(mid)){ 26 ans=mid;l=mid+1; 27 } 28 else 29 r=mid-1; 30 } 31 printf("%d\n",n-ans); 32 return 0; 33 }