题解 P7593 凑数

题目描述

Link

\(T\) 组数据,给定 \(1\)\(n\)\(n\) 个正整数,请问能否恰好选择 \(k\) 个数,使选中的数之和为 \(s\)(每个数只能用 \(1\) 次)。

\(1 \leq T \leq 10^3 ,1 \leq k \leq n \leq 10^9 ,1 \leq s \leq 10^{18}\)

Solution

如果要选出 \(k\) 个数字,使得这 \(k\) 个数字和最小,那么最小值应该是 \(1+2+3+4+\cdots+k\) ,如果 \(s\) 小于这个值,答案就为 No 。同样的,如果选出 \(k\) 个数字,使得这些数字的和最大,最大值应该是 \((n-k+1)+(n-k+2)+\cdots+n\) ,如果 \(s\) 大于这个值,答案也为 No

否则答案一定为 Yes ,可以这样理解:

\(a=\{1,2,3,\cdots ,k\}\) ,此时的和最小。

每次我们都可以选出 \(a_i\) 满足 \(a_i <n-k+i\) ,然后让这个 \(a_i\) 加上一,和就会加上一。

这样的操作最多执行到最大值,也就是 \(a=\{n-k+1,n-k+2,\cdots,n\}\) ,因为每次都加上一,所以在最小值和最大值范围内的所有值都被遍历到了,所以在这个范围内的答案为 Yes

至于求和,可以用等差数列求和公式来求,具体就是:

\[l+(l+1)+\cdots+(r-1)+r=\frac{(l+r)(r-l+1)}{2} \]

然后最小值和最大值都可以 \(\mathcal{O}(1)\) 计算了,每次询问的复杂度就是 \(\mathcal{O}(1)\) ,总时间复杂度 \(\mathcal{O}(T)\)

代码如下(注意开 long long ):

#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
typedef long long LL;
using namespace std;
inline LL get(LL l ,LL r) { //等差数列求和,也就是 l+(l+1)+......+(r-1)+r
    return (l + r) * (r - l + 1) >> 1;
}
int n ,k ,T; LL s;
signed main() {
    scanf("%d" ,&T);
    while (T--) {
        scanf("%d%d%lld" ,&n ,&k ,&s);
        if (get(1 ,k) <= s && s <= get(n - k + 1 ,n)) puts("Yes");
        else puts("No");
    }
    return 0;
}
posted @ 2021-05-12 12:46  recollector  阅读(292)  评论(0编辑  收藏  举报