题目描述
在遥远的提瓦特大陆上,正在筹备一年一度的羽球节,猎鹿人餐厅为犒劳认真筹备的众人,准备了K份甜甜花酿鸡供大家排队领取。
在每一次的排队中,编号为i的角色领取上限为Ai,这意味着他可以领取的甜甜花酿鸡在[1-Ai]范围内。当一个角色领完本次的甜甜花酿鸡,他/她就会回到队列的末尾,直到所有甜甜花酿鸡都被吃完为止。当轮到一个角色领取时,如果所有的甜甜花酿鸡都被领完,那么他/她就要帮大家刷盘子。
贪吃的派蒙每次都吃固定的Ax个甜甜花酿鸡(如果剩下的甜甜花酿鸡的数量比Ax小,那么他就把剩下的都吃完)。我们很容易找到派蒙的编号,Ax比其他所有的Ai都要大。大家都想让派蒙最后留下来刷盘子,请你写一个程序来判断这是否可能。
输入描述:
第一行包含一个整数T(1≤T≤100),表示有T组测试数据。
接下来每组测试数据包含两行。
第一行包含两个整数N和K(2≤N≤10^5
0≤K≤10^8),分别表示人数和甜甜花酿鸡的数量。
第二行包含一个整数Ai(1≤Ai≤10^9),表示队列中编号为i的角色可以领取甜甜花酿鸡的最大数量。始终只有一个最大的Ax。
输出描述:
如果大家能找到一种方案让派蒙刷盘子,那么输出“YES”。否则输出“NO”。
示例1
输入:
1
4 3
1 2 3 2
输出:
YES
示例2
输入:
1
5 8
1 2 3 2 1
输出:
NO
分析
根据题意,此题需要先后解决两个问题:(1)派蒙的位置。(2)什么情况下派蒙刚好吃不到甜甜花酿鸡。派蒙的位置很容易确定,通过遍历一次数组,找到最大元素的位置即确定派蒙的位置。那怎么判断在K个甜甜花酿鸡情况下,派蒙是否刚好吃不到甜甜花酿鸡?
我们可以从小量甜甜花酿鸡开始分析:题意表明,除派蒙外,每个人至少吃1个,我们假定派蒙的位置为pos,如果甜甜花酿鸡数量小于 pos-1 即还没到派蒙,甜甜花酿鸡就吃完了,此情况下,派蒙一定不会刷盘子,派蒙之前的人一定会刷盘子。我们知道每个人最多吃Ai个,如果甜甜花酿鸡数量略微大于pos之前Ai之和,那派蒙一定不会刷盘子,派蒙之后的人一定会刷盘子。(注意:此处情况是略微大于,一会我们将讨论远大于的情况)。也就是说甜甜花酿鸡数量在 pos-1 到 pre_sum[pos-1] 闭区间 派蒙一定会刷盘子。现在我们分析甜甜花酿鸡数量很大的情况,我们可以这么想:经过rounds轮后,剩余的甜甜花酿鸡数量已经不够下一轮了,此时情况与小量情况一模一样。至此,此题基本就解决了。
现在有一个重要问题没有解决:每轮中,整个队的人吃多少个甜甜花酿鸡?我们可以假定整个队的人每次吃个特殊数,即极大数或者极小数。因为只有这样我们才能得到更多的信息来分析。一个队吃的最小值为min_sum,最大值为:max_sum。我们假定一个队吃最小值,这样可以得到一个重要信息:K个甜甜花酿鸡最多rounds轮后就吃完了。同时我们将剩下left个。现在我们判断left。若left比 pos-1 小,也就是上一轮多吃了,因为每次只吃最小,所以我们只需left加min_sum(也就是说多吃了一轮)。同时,rounds减一,然后再判断left。若left比pre_sum[pos-1]大,也就是说在rounds轮中吃少了,我们需要将多出部分分到rounds轮中。怎么将多出部分分到rounds轮中?具体分法我们可以不用管,但是我们发现,若left - pre_sum[pos-1] 大于 rounds*(max_sum - min_sum),即之前都吃最大时,仍有剩余,则派蒙一定不会刷盘子。
具体实现代码如下:
#include<algorithm>
#include<vector>
#include<stack>
#include<unordered_map>
#include<iostream>
#include<unordered_set>
#include<cstring>
#include<set>
#include<map>
#include<queue>
#include<ctype.h>
#define MAX 111111
const int MOD = 1e9;
using namespace std;
typedef long long int ll;
ll arr[MAX];
ll pre_sum[MAX];
ll left_low;
ll left_high;
int n,k,t,pos;
void print();
int main(void)
{
int i;
cin >> t;
while(t--)
{
pos = 1;
cin >> n >> k;
for(i=1; i<=n; ++i)
{
cin >> arr[i];
if(arr[i] > arr[pos])
pos = i;
pre_sum[i] = arr[i] + pre_sum[i-1];
}
left_low = pos-1; //派蒙刷盘子的最小值
left_high = pre_sum[pos-1]; //派蒙刷盘子的最大值
print();
}
return 0;
}
void print()
{
ll min_sum = n-1+arr[pos]; //该队能吃的最小值
ll max_sum = pre_sum[n]; //该队能吃的最大值
ll rounds = k/min_sum; //最多rounds轮
ll left = k%min_sum; //剩余量
ll temp = 0;
ll max_minus = 0;
while(1) //循环判断每一次left
{
if(left>= left_low && left<= left_high)
{
printf("YES\n");
return;
}
if(left > left_high)
{
temp = left - left_high;
max_minus = rounds*(max_sum - min_sum); //之前均吃最大
if(temp > max_minus) // 若之前均吃最大值后,仍有剩余
{
printf("NO\n");
return;
}
}
if(left < left_low)
{
if(!rounds) //若rounds为零,left仍小于left_low
{
printf("NO\n");
return;
}
left += min_sum;
--rounds;
}
}
return;
}
以上若有错误,请告知作者。感激不尽!