USACO 2017 December Contest Platinum T3: Greedy Gift Takers

题目大意

有 N(1N1e5)头牛按顺序排成一列,编号从 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 }

 

posted @ 2019-07-24 08:30  LI_dox  阅读(252)  评论(0编辑  收藏  举报