【C语言】贪心吃糖

相信你是最棒哒

文章目录

题目描述

正确代码

总结


题目描述

Timur有n颗糖果。第𝑖i颗糖果的糖分数量为ai​。因此,通过吃第𝑖i颗糖果,Timur消耗的糖分数量为ai​。

Timur会向你询问𝑞q个关于他糖果的问题。对于第𝑗个问题,你需要回答,为了达到糖分数量大于或等于𝑥𝑗​,他需要至少吃多少颗糖果,如果不可能达到这样的糖分数量,则输出-1。换句话说,你需要输出最小可能的𝑘k,使得吃𝑘k颗糖果后,Timur消耗的糖分至少为𝑥𝑗,或者说明不存在这样的k。

请注意,他不能吃同一颗糖果两次,并且各个问题是独立的(Timur可以在不同问题中使用同一颗糖果)。

输入

第一行输入一个整数𝑡t(1≤𝑡≤1000)--- 测试用例的数量。接下来是每个测试用例的描述。

第一行包含2个整数𝑛n和𝑞q(1≤𝑛,𝑞≤1.5⋅105)--- Timur拥有的糖果数量和你需要输出答案的查询数量。

第二行包含n个整数𝑎1,𝑎2,...,𝑎𝑛​(1≤𝑎𝑖≤104)--- 分别是每颗糖果中的糖分数量。

接下来是q行。

接下来的每一行包含一个整数𝑥𝑗xj​(1≤𝑥𝑗≤2⋅1091≤xj​≤2⋅109)--- Timur想要在给定查询中达到的糖分数量。

保证所有测试用例中n和𝑞q的总和不超过1.5⋅105。

输出

对于每个测试用例,输出q行。对于第𝑗j行,输出Timur需要吃多少颗糖果才能达到糖分数量大于或等于𝑥𝑗,如果无法达到这样的糖分数量,则输出-1。

示例

Inputcopy Outputcopy
3 8 7 4 3 3 1 1 4 5 9 1 10 50 14 15 22 30 4 1 1 2 3 4 3 1 2 5 4 6 1 2 -1 2 3 4 8 1 1 -1

注意

对于第一个测试用例:

对于第一个查询,Timur可以吃任何一颗糖果,他将达到所需的糖分数量。

对于第二个查询,Timur可以通过吃第77和第88颗糖果达到至少1010的糖分数量,消耗的糖分数量为1414。

对于第三个查询,无法找到答案。

对于第四个查询,Timur可以通过吃第77和第88颗糖果达到至少1414的糖分数量,消耗的糖分数量为1414。

对于第二个测试用例:

对于第二个测试用例的唯一查询,我们可以选择第三颗糖果,Timur将获得恰好33的糖分。同样可以通过选择第四颗糖果获得相同的答案。


正确代码

注释版

#include <iostream>  // 包含输入输出流的头文件
#include <algorithm> // 包含算法的头文件,这里主要用于sort函数

using namespace std; // 使用标准命名空间

const int T=1e6+10; // 定义一个常量T,表示数组的最大大小,1e6+10表示100万加10
int a[T]; // 定义一个全局数组a,用于存储输入的整数
long long t[T]; // 定义一个全局数组t,用于存储前缀和

// 定义一个比较函数cmp,用于sort函数的自定义比较
bool cmp(int a,int b)
{
    return a>b; // 如果a大于b,则返回true,这样sort函数会按照降序排列数组
}

int main() 
{
    int tt; // 定义一个变量tt,用于存储测试用例的数量
    scanf("%d",&tt); // 读取测试用例的数量

    while(tt--) // 对于每个测试用例
    {
        int n,q; // 定义两个变量n和q,分别表示数组的大小和查询的次数
        scanf("%d%d",&n,&q); // 读取数组的大小和查询的次数

        for(int i=1;i<=n;i++) // 循环读取数组的元素
        {
            scanf("%d",&a[i]); // 读取第i个元素并存储在数组a中
        }

        sort(a+1,a+n+1,cmp); // 对数组a进行降序排序,注意数组是从1开始的,所以是a+1到a+n+1,左闭右开
        t[0]=0; // 初始化前缀和数组的第一个元素为0
        for(int i=1;i<=n;i++) // 计算前缀和
        {
            t[i]=t[i-1]+a[i]; // 前缀和的第i个元素等于前一个元素加上当前元素
        }

        while(q--) // 对于每个查询
        {
            int x; // 定义一个变量x,用于存储查询的值
            scanf("%d",&x); // 读取查询的值

            int l=1,r=n; // 定义两个变量l和r,用于二分查找
            while(l<r) // 二分查找的过程
            {
                int mid=(l+r)/2; // 计算中间位置
                if(t[mid]>=x) // 如果前缀和的中间位置大于等于查询值
                {
                    r=mid; // 则缩小右边界
                }
                else
                {
                    l=mid+1; // 否则缩小左边界
                }
            }
            if(t[r]>=x) // 如果前缀和的左边界大于等于查询值
            {
                printf("%d\n",r); // 输出位置
            }
            else
                printf("-1\n"); // 如果没有找到,则输出-1
        }
    }
}

简洁版

#include <iostream>
#include <algorithm>
using namespace std;
const int T = 1e6 + 10;
int a[T];
long long t[T];
bool cmp(int a, int b)
{
	return a > b;
}
int main()
{
	int tt;
	scanf("%d", &tt);
	while (tt--)
	{
		int n, q;
		scanf("%d%d", &n, &q);
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);

		}
		sort(a + 1, a + n + 1, cmp);
		t[0] = 0;
		for (int i = 1; i <= n; i++)
		{
			t[i] = t[i - 1] + a[i];
		}


		while (q--)
		{
			int x;
			scanf("%d", &x);
			/*	int sum = 0;
				bool mark=0;
				for(int i=0;i<n;i++)
				{
					sum=sum+a[i];
					if(sum>=x)
					{
						mark=1;
						printf("%d\n",i+1);
						break;
					}
				}
				if(mark==0)
					printf("-1\n");   */		
			int l = 1, r = n;
			while (l < r)
			{
				int mid = (l + r) / 2;
				if (t[mid] >= x)
				{
					r = mid;
				}
				else
				{
					l = mid + 1;
				}
			}
			if (t[r] >= x)
			{
				printf("%d\n", r);
			}
			else
				printf("-1\n");
		}
	}
}

被注释掉的部分是一个暴力解法的示例:

int sum=0; // 初始化一个变量sum,用于存储从数组开始到当前索引的元素之和
bool mark=0; // 初始化一个布尔变量mark,用于标记是否找到了满足条件的索引

// 从数组的第一个元素开始遍历,直到数组的最后一个元素
for(int i=0; i<n; i++)
{
    sum=sum+a[i]; // 将当前索引i的元素值加到sum上
    // 检查当前的和sum是否大于或等于给定的值x
    if(sum >= x)
    {
        mark=1; // 如果找到了满足条件的索引,将mark设置为1
        printf("%d\n", i+1); // 输出满足条件的索引+1(因为数组索引从0开始,所以需要+1)
        break; // 跳出循环,因为已经找到了满足条件的索引
    }
}

// 在循环结束后,检查mark的值
if(mark==0) // 如果mark仍然是0,说明没有找到满足条件的索引
    printf("-1\n"); // 输出-1,表示没有找到这样的索引

这种方法被称为"暴力"的原因是它直接检查了所有可能的元素组合,直到找到答案。对于小规模的数据,这种方法可能是可接受的,但是当数据量增大时,这种方法的效率会显著下降,因为它的时间复杂度是O(n),其中n是数组的大小。这意味着对于每个查询,都需要遍历整个数组,这在最坏的情况下是非常耗时的。

相比之下,代码中未被注释的部分使用了前缀和和二分查找的组合,这是一种更高效的解决方案,因为它的时间复杂度是O(log n),其中n是数组的大小。这种方法通过减少必要的比较次数来提高效率,特别是在处理大数据集时。


总结

这段代码的主要思想是使用前缀和和二分查找来快速解决问题。首先,将数组降序排序,然后计算前缀和。对于每个查询,使用二分查找找到前缀和大于或等于查询值的最小索引,这个索引就是数组中小于或等于查询值的最大元素的位置。如果没有找到这样的元素,则输出-1。

相关推荐
不想当程序猿_3 分钟前
【蓝桥杯每日一题】扫描游戏——线段树
c++·算法·蓝桥杯·线段树·模拟
明月看潮生13 分钟前
青少年编程与数学 02-004 Go语言Web编程 01课题、Web应用程序
开发语言·青少年编程·编程与数学·goweb
虾球xz20 分钟前
游戏引擎学习第54天
学习·游戏引擎
cwtlw29 分钟前
SpringMVC的使用
java·开发语言·笔记·学习·其他
Jambo!34 分钟前
Visual studio中C/C++连接mysql
c语言·c++·mysql·visual studio
明月看潮生39 分钟前
青少年编程与数学 02-004 Go语言Web编程 07课题、WebSockets
开发语言·青少年编程·golang·编程与数学
SoraLuna42 分钟前
「Mac畅玩鸿蒙与硬件46」UI互动应用篇23 - 自定义天气预报组件
开发语言·算法·macos·ui·华为·harmonyos
KaiPeng-Nie43 分钟前
代码随想录day22 | 回溯算法理论基础 leetcode 77.组合 77.组合 加剪枝操作 216.组合总和III 17.电话号码的字母组合
java·算法·leetcode·剪枝·回溯算法·回归算法·递归函数
打不了嗝 ᥬ᭄1 小时前
P8795 [蓝桥杯 2022 国 A] 选素数
算法·leetcode·职场和发展·蓝桥杯·图论
旷野..1 小时前
谁说C比C++快?
java·c语言·c++