【ACM】算法题-连续和(数学)(递归)(动态规划)(二分法)(多解)

ACM数学专题:连续和

题目描述

对一个给定的自然数M,求出所有的连续的自然数段(连续个数大于1),这些连续的自然数段中的全部数之和为M。
例子:1998+1999+2000+2001+2002 = 10000,所以从1998到2002的一个自然数段为M=10000的一个解。

输入

包含一个整数的单独一行给出M的值(10 <= M <= 2,000,000)

输出

每行两个自然数,给出一个满足条件的连续自然数段中的第一个数和最后一个数,两数之间用一个空格隔开,所有输出行的第一个按从小到大的升序排列,对于给定的输入数据,保证至少有一个解。
样例输入

15

样例输出

1 5
4 6
7 8

解析

本题解法多,思路清晰,重点在于提高程序运行速度,否则,所给的M值一较大,程序会崩就很正常,那么用数学的方法分析来优化算法再巧妙不过。凡事有个过程,我们在理解清楚题目要求后,或多或少的有一点点idea就该去尝试与实践,下面我给出四份解决思路,按照运行速度的排序就有:

时间复杂度:
O(n^3)
O(n^2)
O(nlog(n))
O(sqrt(n))

思路一: 三重循环

( O ( n 3 ) ) (O(n^3)) (O(n3))

#include <bits/stdc++.h>
#include<iostream>
using ll=long long;//这里用long long 64位整数才能满足大数要求。
int main(){
ll M;
cin>>M;
ll a,b;
   for (a = 1; a < M; a++) {
        for (b = a + 1; b < M; b++) {
            ll s=0;
            for (ll i = a; i <= b; i++) {
                s += i;
            }
            if (M == s)cout << a << " " << b << endl;
        }
    }
    return 0;
    }
思路一分析:操作简单,每重循环遍历,最后按条件满足a~b连续和等于M后输出a与b的值空格换行即可。但是可搜索范围太小,因为是三重循环,在一秒内,只可以解决一千的规模,一个数为一万,三次方后就有十二位了,再大一点的数就愈发慢至直接崩溃,而题目给出的M的范围可至两百万,此解自然过于渺小与无力,下一步我们来逐渐优化。
思路二:二重循环

O ( n 2 ) O(n^2) O(n2)

#include <bits/stdc++.h>
#include<iostream>
using ll=long long;//这里用long long 64位整数才能满足大数要求。
int main(){
ll M;
cin>>M;
ll a,b;
    for (a = 1; a < M; a++) {
        for (b = a + 1; b < M; b++) {
            ll s = 0;
            s = (b - a + 1) * (a + b) / 2;//等差数列性质
            if (M == s)cout << a << " " << b << endl;
        }
    }
    return 0;
    }
思路二分析:与思路一差不多,但我们减少了一重循环,且是通过利用我们的基础数学知识,就开始巧妙起来了,从a加到b,是等差数列求和,利用公式就条件列得正确清晰了,可是,我们依然得考虑数据的呀,这样能解决的规模扩大了,同学们一般的题还是可以过关了,在这里是达不到题目要求的,竞赛嘛,算法的思想你得体现出它的魅力,我们进阶下一思路。
思路三:前缀和与二分查找

( O ( n ∗ l o g ( n ) ) ) (O(n*log(n))) (O(nlog(n)))

#include <bits/stdc++.h>
#include<iostream>
using ll=long long;//这里用long long 64位整数才能满足大数要求。
const ll maxn = 2e6 + 10;
ll sum[maxn];
void search_nlogn(ll n) {
    sum[1] = 1;
    for (int i = 2; i <= n; i++) {
        sum[i] = sum[i - 1] + i;
    }
    for (int i = 0; i <= n; i++) {
        ll x = sum[i];
        ll y = x + n;
        int j = lower_bound(sum, sum + n, y) - sum;
        if (sum[j] == x + n && j > (i + 1)) {//筛掉不符合的数据,保持严谨
            cout << i + 1 << " " << j << endl;
        }
    }
}
int main(){
    ll M;
    cin>>M;
    ll a,b;
    search(M);
    return 0;
    }
思路三分析:第一步:做一个前缀和的表

s ( 1 ) = 1 ; s(1)=1; s(1)=1;
s ( 2 ) = 1 + 2 ; s(2)=1+2; s(2)=1+2;
s ( 3 ) = 1 + 2 + 3 s(3)=1+2+3 s(3)=1+2+3
s ( 4 ) = 1 + 2 + 3 + 4 s(4)=1+2+3+4 s(4)=1+2+3+4
s ( n ) = 1 + 2 + . . . . . . + n s(n)=1+2+......+n s(n)=1+2+......+n

第二步:进行二分查找lower_bound()
寻找前缀和数列中两个数之差就是M的值啦,二分算法在此处体现,你做到这里呢,才能算是数据结构的运用啦,本方法呢在竞赛题中一般已经可以过了哦,是此题不错的解法,可是,你想拥有更强的一份代码,更高效提速嘛,我们还有一种。。。
思路四:因子分解

( O ( s q r t ( n ) ) (O(sqrt(n)) (O(sqrt(n))

#include<bits/stdc++.h>

using namespace std;
#define endl '\n' //换行字符\n确实比endl要快哦
using ll=long long;

void search(ll n) {
    ll m = 2 * n;
    for (ll p = sqrt(m); p >= 1; p--) {
        if (m % p != 0)continue;//要满足是m的因子才行
        ll q = m / p;//有p自然得出q
        if ((p + q) % 2 == 0)continue;//筛掉一些因子和为偶的,比如16的输出结果是7和8就有问题呢
        ll a = (q - p + 1) / 2;//
        ll b = (q + p - 1) / 2;//带入公式求出
        if (a < b)cout << a << " " << b << endl;
    }
}

int main() {
    ios::sync_with_stdio(false);//
    cin.tie(nullptr);//
    cout.tie(nullptr);此处也是提高程序运行速度,减少缓冲的哦,之前有讲
    ll M;
    cin >> M;
    search(M);
    return 0;
}
思路四分析:此处用到的是因子分解的数学知识(-_-!!!数学好重要),首先在已知前面等差数列求和的公式下:

2 ∗ s = ( b − a + 1 ) ∗ ( a + b ) 2*s=(b-a+1)*(a+b) 2s=(ba+1)(a+b)设置p,q为2*s的两个因子
p = ( b − a + 1 ) ∗ ( a + b ) p=(b-a+1)*(a+b) p=(ba+1)(a+b)
q = ( a + b ) q=(a+b) q=(a+b)

因子分解的时间复杂度只有Osqrt(n),对速度,和很大范围内的数求解实在是太友好啦,题中M两百万的范围完全不在话下,再大些也能支持,是我在本题中所找到的最优解啦,你们呢?

小Tips:

一般的题,思路二即可过关,还可以看到运用一点数学思维,竞赛考的话思路三才可以过哦,因为运用了二分法来提速后数据就OK了,思路四就更好了,安利安利,很多时候呢,不是你code的越多,越复杂就是越好的,最适合,最高效才深得人心呢!!
  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值