Skip to content

哈喽,主人!今天由咱来陪你分析这道题,喵~ 这道题看起来有点绕,但只要找到其中的小窍门,就像找到猫薄荷一样,一下子就豁然开朗啦!

题目大意

这道题是说呀,给你三个正整数 n, k, 和 x

  • n 代表咱可以从一个数字池 1, 2, 3, ..., n 中挑选数字。
  • k 代表咱需要挑选出 k不同的数字。
  • x 是一个目标和,咱选出的 k 个数字加起来必须正好等于 x

任务就是判断,到底存不存在这样一种挑选方案。如果存在,就告诉人家 "YES",如果不存在,就说 "NO",喵~

举个例子:n=5, k=3, x=10。咱可以从 1, 2, 3, 4, 5 中选 3 个不同的数。比如,咱可以选 {2, 3, 5},它们的和是 2+3+5=10,正好等于 x!所以答案就是 "YES"。

题解方法

主人,要解决这个问题,咱不需要真的去一个个组合尝试,那样太慢啦,会累趴的。咱可以换个思路,想想看,用这 k 个不同的数,我们能凑出的最小总和最大总和分别是多少呢?

最小总和 (min_sum)

要想让 k 个不同数字的和最小,咱肯定要选最小的那 k 个数啦,对不对?也就是 1, 2, 3, ..., k。 它们的和就是 1 + 2 + 3 + ... + k。这是一个经典的等差数列求和,公式是 k * (k + 1) / 2。 所以,min_sum = k * (k + 1) / 2。任何 k 个数的组合,它们的和都不可能比这个数更小了,喵~

最大总和 (max_sum)

那反过来,要想和最大,咱就要选最大的那 k 个数啦。也就是 n, n-1, n-2, ..., n-k+1。 它们的和就是 n + (n-1) + ... + (n-k+1)。这也是一个等差数列,有 k 项,首项是 n-k+1,末项是 n。 根据等差数列求和公式 (首项 + 末项) * 项数 / 2,我们可以得到: max_sum = ( (n - k + 1) + n ) * k / 2 = (2*n - k + 1) * k / 2。 任何 k 个数的组合,它们的和也都不可能比这个数更大了,nya~

关键的思路

现在咱知道了能凑出的和的范围是 [min_sum, max_sum]。那是不是这个范围里的所有整数都能凑出来呢?

答案是肯定的,喵!

主人你想想,我们从最小和的组合 {1, 2, ..., k} 开始。如果咱想让总和增加 1,可以怎么做呢?咱可以尝试把集合里最大的数 k 换成 k+1(只要 k+1 不大于 n 且不在集合里)。这样总和就增加了 1。通过这种方式,每次把一个选中的数 a_i 换成 a_i + 1(只要满足条件),我们就能让总和一点一点地增加。

这个过程可以一直持续,直到我们把 {1, 2, ..., k} 这个组合,变成了 {n-k+1, ..., n-1, n} 这个最大和的组合。既然可以一步步地增加 1,那就意味着从 min_summax_sum 之间的每一个整数值都是可以被凑出来的!

所以,咱的算法就非常简单啦:

  1. 计算出 min_sum
  2. 计算出 max_sum
  3. 判断目标 x 是否在 [min_sum, max_sum] 这个闭区间内。
    • 如果是,就说明一定能找到一种组合,输出 "YES"。
    • 如果不是,那就肯定找不到,输出 "NO"。

是不是很巧妙呀? nya~

题解代码分析

好啦,现在让咱一起来看看这份代码是怎么实现咱的想法的吧,喵~

cpp
#include <iostream>

// 这个函数处理单个测试用例
void solve() {
    // 读入 n, k, x
    // 这里用 long long 是因为 x 和计算出的和可能会非常大,
    // 就像咱的尾巴一样长,用小小的 int 会装不下哦!
    long long n, k, x;
    std::cin >> n >> k >> x;

    // 计算最小可能和
    // 就是 1 + 2 + ... + k 的和
    long long min_sum = k * (k + 1) / 2;

    // 计算最大可能和
    // 就是 (n-k+1) + ... + n 的和
    // 公式是 (2*n - k + 1) * k / 2
    long long max_sum = (2 * n - k + 1) * k / 2;

    // 核心判断逻辑
    // 只要目标 x 在 [min_sum, max_sum] 这个区间内,就一定有解
    if (x >= min_sum && x <= max_sum) {
        std::cout << "YES\n";
    } else {
        std::cout << "NO\n";
    }
}

int main() {
    // 这两行是为了加速输入输出,在有很多测试用例的时候能跑得更快,像猎豹一样!
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    // 读取测试用例的数量
    int t;
    std::cin >> t;

    // 循环处理每个测试用例
    while (t--) {
        solve();
    }

    return 0;
}

代码的逻辑和咱上面分析的思路完全一样,先算出最小和与最大和,然后判断 x 是否落在这个区间里。因为 n, k, x 的值可能很大,导致计算出的和也很大,所以必须使用 long long 类型来存储,防止数据溢出。

知识点介绍

这道题里藏着一个数学小魔法哦,叫做 等差数列求和 (Arithmetic Series Summation),喵~

什么是等差数列?

等差数列就是一串数字,其中任意相邻两个数的差都是一个常数。比如 1, 3, 5, 7, 9 就是一个等差数列,公差是 2。

求和公式

求和公式有两个常用的:

  1. Sum = n * a1 + n * (n-1) * d / 2
    • n: 项数
    • a1: 首项
    • d: 公差
  2. Sum = (a1 + an) * n / 2
    • n: 项数
    • a1: 首项
    • an: 末项

第二个公式更直观,也更适合这道题。就像一排排整齐的猫爪印,把第一个和最后一个配对,第二个和倒数第二个配对,它们的和都是一样的,所以用 (首项 + 末项) * 项数 / 2 就能快速算出总和啦!

  • 计算 min_sum:

    • 数列: 1, 2, ..., k
    • 首项 a1 = 1, 末项 an = k, 项数 n = k
    • min_sum = (1 + k) * k / 2
  • 计算 max_sum:

    • 数列: n-k+1, ..., n-1, n
    • 首项 a1 = n-k+1, 末项 an = n, 项数 n = k
    • max_sum = ((n-k+1) + n) * k / 2 = (2*n - k + 1) * k / 2

掌握了这个小知识,解决这类问题就会变得非常轻松哦!希望这次的讲解能帮到主人,喵~ 如果还有问题,随时可以再来找咱玩!

Released under the MIT License.