题解 P7593 凑数
题目描述
\(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;
}