鸡国福利
时间限制: 3000 ms
空间限制: 262144 KB
【题目描述】
鸡国为了表彰鸡国每一只鸡在过去一年的优秀表现,打算在接下来的 n 天中每天给鸡国的一只鸡发 1 袋或者 2 袋“鸡币”(鸡国的通用货币)作为福利。国王要求每天来领钱鸡互不相同,即来领过钱的鸡不能再来,否则将受到严厉的处罚。
但聪明的鸡国老百姓侦察后发现国王每天发的钱袋子里面装的钱数量是不一样的(同一天的相同),第 i 天发的每一袋钱为 ai元。如果第 i 天来领钱的鸡领 1 袋钱,它可以获得ai元的“鸡币”,如果它领 2 袋钱,则可以获得 2×ai元“鸡币”,当然它也可以放弃,则第i 天的钱国王收回国库。
由于鸡国生活条件优越和鸡的贪念等原因,当第 i 天领钱的鸡同时满足以下两个条件时
它才会感到幸福:
(1)领到的钱不能低于鸡国的平均收入 m 元。
(2)要跟它前面领了钱且感到幸福的鸡一样幸福或者更幸福。
仁慈的国王希望鸡国的每一只鸡都能感到幸福,请你帮国王规划一下在这 n 天中怎样给每一只发钱才能让最多的鸡感到幸福?
【输入】
输入共 2 行。
第 1 行输入两个整数 n 和 m,分别表示发钱的天数(或理解为来领钱的鸡数)和鸡国的平均收入。
第 2 行 n 个正整数 ai(1≤i≤n),依次表示第 i 天发的一袋钱中的“鸡币”为 ai元。
【输出】
输出 1 行一个整数,表示最多可以让多少只鸡感到幸福。
【样例输入与输出】
Input1:
2 1
2 1
Output1:
2
Input2:
3 2
1 2 3
Output2:
3
Input3:
6 4
1 2 1 2 1 5
Output3:
3
【数据范围限制】
【提示】
Sample1:
样例 1 中,可以让第 1 天来领钱的第 1 只鸡领 2 元(1 袋),第 2 天来领钱的第 2 只鸡领 2 元(2 袋),最多可以有 2 只鸡感到幸福。
Sample2:
样例 2 中,由于鸡国的平均收入为 2 元,所以领 1 元及以下的鸡是不会感到幸福。可以让第 1 天来领钱的第 1 只鸡领 2 元(2 袋),第 2 天来领钱的第 2 只鸡领 2 元(1 袋),第 3 天来领钱的第 3 只鸡领 3 元(1 袋),最多可以有 3 只鸡感到幸福。
Sample3:
样例 3 中,由于鸡国的平均收入为 4 元,所以第 1 天,第 3 天,第 5 天来领钱的鸡不管领 1 袋钱,或者领 2 袋钱,或者不领都不会感到幸福。可以让第 2 天来领钱的第 2 只鸡领4 元(2 袋),第 4 天来领钱的第 4 只鸡领 4 元(2 袋),第 6 天来领钱的第 6 只鸡领 5 元(1袋),最多可以有 3 只鸡感到幸福。
【题解】
相信大家看到这道题时,会想到用一种神奇的 O(n) 的方法——贪心!
不过嘛……贪心是会爆0的!(连样例都过不了)
接着就会想到更好的方法:DFS,BFS,还有DP(想到前两个的是因为时间限制吗)
我就来讲一讲DP的方法吧!
首先我讲一下我比赛时的方法(也是用DP)
设f[i][1]为到第i只鸡的时候选了1份“鸡币”时感到幸福的鸡的数量的最大值(第i只鸡只要得到的“鸡币”数量大于平均收入,都会感到幸福)
而f[i][2]就是选了2份“鸡币”的最大值。
然后就得了70分了(时间超限,不过是所有70分中跑得最快的)
现在我就来讲一讲正解吧!(什么?现在才开始讲正解?!)
正解就是打最长不下降子序列,不过要用\(O(n\log_2n)\)的打法。
但为什么要用最长不下降子序列呢?
其实我们很容易就可以发现,答案要我们求出的那一串鸡所得到的“鸡币”数量是不下降的(如果是有下降的话,那么后面的鸡就没有前面的鸡那么幸福)
so,我们就只用求出一个最长不下降子序列就好了。
相信大家都会用O(n^2)的方法做最长不下降子序列(不会做就算了,反正那个方法已经被淘汰),但这样只能拿到70分~下面讲\(O(n\log_2n)\)的方法
我们可以设f[i]为长度为i的所有最长不下降子序列中,末尾(也就是子序列中的最大值)最小的那串的末尾。
然后这个 f 数组就不会出现f[i]比f[j]大的情况了(i< j),原因大家都懂吧!
如果\(f[i]>f[j]\)的话,那么f[i]不应该接在f[j]的后面了么?
然后我就来说一下怎样去维护好这个 f 数组吧!
我们可以定义一个变量len,用于记录最长不下降子序列的长度,输出时直接输出len就好了。
对于每一只新循环到的“鸡币”数量a[i],我们都作以下处理:
- 首先,先看一下a[i]是否比m大,如果是的话就看看a[i]是否大于等于f[len]。
- 如果a[i]≥f[len],那该怎么办呢?那就说明a[i]可以接在这个序列后面,同时最长的长度len也要加1。
- 如果a[i]无法接在序列后面,那就要考虑用它们改某一个f[j]的值了。
- 首先,我们要找到第一个大于a[i]的f[j]——注意,是第一个大于a[i]的f[j]!查找的时候一定要用二分,因为用循环找的话会慢很多(二分的时间复杂度是O(log n),循环则是O(n))
- 找到了之后我们该怎么办呢?这时,我们会发现,f[j-1]肯定是小于等于a[i]的,而f[j]又大于a[i],那我们是不是可以把a[i]接在f[j-1]的后面呢?这当然是可以的!那既然a[i]< f[j]了,而把a[i]接在f[j-1]后面形成的序列又是不下降的,我们就可以用a[i]代替f[j]了(因为a[i]比f[j]更小,也就更优了)
- 最后再考虑a[i]*2的情况就好了(打if语句时思路要清晰,不要弄错顺序了)!
下面我来讲一下一些让部分同学感到疑惑的问题吧!
问题一:如果a[i]已经接在序列的末尾了,那么a[i]2还要处理吗?
解答:这肯定是要处理的!不过a[i]2绝对是不能接在队列的后面的(除非i等于1)
问题二:如果a[i]已经修改了某一个f[j]的值了,那a[i]*2还可以改另一个f[j]的值吗?
比如说下面这种情况:
目前的 f 数组:3 4 5
a[i] 和 a[i] *2:1 2
当我们用a[i]代替了f[1]时,那到了处理a[i]2时,f 数组就会变成这个样子:1 4 5
再被a[i]2处理一下,f 数组不就会变成 1 2 5了吗?这样会不会不合法呢?
解答:这样做是对的,不用怕它不合法!
所以,大家都懂了吧!不懂请发私信。
后面我就附上标程了,思路正确可是却莫名其妙出错的同学可以看一下代码。
#include<cstdio>
using namespace std;
int f[1001000],a[1001000];
int findbigger(int x,int r)//这里是二分查找的部分
{
int l=1,mid=(l+r)/2,want=0;//记得初始化返回值!(这里坑惨了我)
while(l<=r) //万恶的C++!竟然没有给变量赋初值!
{
mid=(l+r)/2;
if(f[mid]>x)
{
want=mid;
r=mid-1;
}
else l=mid+1;
}
return want;
}
int main()
{
int i,j,n,m,k,t,len=0;
bool bz;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(i=1;i<=n;i++)//思路必须严谨,不然处理的顺序错了的话,后果很严重!
{
if(a[i]>=m)
{
if(a[i]>=f[len])
{
f[++len]=a[i];
}
else
{
k=findbigger(a[i],len);
f[k]=a[i];
}
}
if(a[i]*2>=m)
{
if(a[i]*2>=f[len]&&(a[i]<f[len]||i==1))//这个细节我想了一个下午
{
f[++len]=a[i]*2;
}
else
{
t=findbigger(a[i]*2,len);
f[t]=a[i]*2;
}
}
}
printf("%d\n",len);
return 0;
}