2024-10-31每日一题
连续自然数和
题目描述
对一个给定的正整数 \(M\),求出所有的连续的正整数段(每一段至少有两个数),这些连续的自然数段中的全部数之和为 \(M\)。
例子:\(1998+1999+2000+2001+2002 = 10000\),所以从 \(1998\) 到 \(2002\) 的一个自然数段为 \(M=10000\) 的一个解。
输入格式
包含一个整数的单独一行给出 \(M\) 的值(\(10 \le M \le 2,000,000\))。
输出格式
每行两个正整数,给出一个满足条件的连续正整数段中的第一个数和最后一个数,两数之间用一个空格隔开,所有输出行的第一个按从小到大的升序排列,对于给定的输入数据,保证至少有一个解。
样例输入
10000
样例输出
18 142
297 328
388 412
1998 2002
思路
做法有很多,其中比较容易想到的是以二分为基础的做法(还不会二分的同学建议速速学习,不管会不会都建议背一下二分模板)
对于每组解,都是由左右边界构成的(形如\([L, R]\),L为左边界,R为右边界)我们可以从1~n枚举每一个L和R,用前缀和或者高斯定理进行求和看看是否为n(实际上L、R必然≤n/2,范围可以缩小),但这样时间复杂度就到了\(O(n^2)\),会超时。
所以我们要用二分进行优化,我们枚举每个L,对于每个L,最多存在一个R使得\([L, R]\)之和为n,我们可以对R进行二分查找(用\([L,R]\)之和与n的大小关系作为判断条件),求\([L,R]\)之和可以用前缀和、高斯定理((首项+末项)×项数÷2)等方法。
附赠二分板子
bool check(int mid) {
if(...)return 1;
else return 0;
}
.....
.....
int l=L,r=R,mid;//L为下边界,R为上边界
while(l<r) {
mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
.....
.....
-----------------------------------------------------------------------------
//check(mid)返回1表示满足条件,返回0就是不满足条件,不管返回什么,每次check以后mid的取值区间都会收缩,最终收拢到最终答案,例如在这一题里面,check(mid)写成函数就是
bool check(int mid) {
if((i+i+mid)*(mid+1)/2>=n)return 1;
else return 0;
}
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n;
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n/2;i++) {
int l=1,r=n,mid;
while(l<r) {
mid=(l+r)/2;
if((i+i+mid)*(mid+1)/2==n) {
printf("%lld %lld\n",i,i+mid);
break;
} else if((i+i+mid)*mid/2>n) {
r=mid;
} else {
l=mid+1;
}
}
}
return 0;
}