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;
}
posted @ 2024-10-31 11:30  Sonatto  阅读(24)  评论(0编辑  收藏  举报