第八届“图灵杯”NEUQ-ACM程序设计竞赛个人赛(同步赛)G - 贪吃的派蒙(附详细讲解)

本文探讨了一个关于派蒙在队伍中是否会被分配到刷盘子任务的问题。通过数学分析和编程实现,给出了判断派蒙是否会成为刷盘子者的算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述
在遥远的提瓦特大陆上,正在筹备一年一度的羽球节,猎鹿人餐厅为犒劳认真筹备的众人,准备了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;
}

以上若有错误,请告知作者。感激不尽!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值