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(n∗log(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)
2∗s=(b−a+1)∗(a+b)设置p,q为2*s的两个因子
p
=
(
b
−
a
+
1
)
∗
(
a
+
b
)
p=(b-a+1)*(a+b)
p=(b−a+1)∗(a+b)
q
=
(
a
+
b
)
q=(a+b)
q=(a+b)