相信你是最棒哒
文章目录
题目描述
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。