洛谷P1147 连续自然数和
题目解法:
这题是个大水题,但是我们看到了两种解法:
法一①:考虑前缀和做法。
首先我们读题,发现其要求的是连续自然数和。连续自然数和,那么我们可以用前缀和来处理这些自然数的和。于是我们得到了一个显然的做法:(30pts)直接枚举。
Code:
#include<iostream> #include<cstdio> #define ll long long #define maxn 2000001 using namespace std; int m,s[maxn]; int main(){ cin>>m; s[0]=0; for(int i=1;i<=m;i++){ s[i]=s[i-1]+i; } for(int i=1;i<=m;i++){ for(int j=1;j<i;j++){ if(s[i]-s[j-1]==m){ cout<<j<<" "<<i<<endl; } } } return 0; }
法一②:考虑前缀和做法的优化版本。我们发现,在上一个阶段的代码上,我们可以对内层循环j做一些处理。比如,我们可以对这个j进行二分答案,得到一个j,然后从i到j,就是答案要求的区间。
Code:
#include<iostream> #include<cstdio> #define maxn 2000001 #define int long long using namespace std; int m,s[maxn]; int check(int ll,int rr,int k){ if(ll>rr) return -1; int mid=(ll+rr)/2; if(s[mid]-k==m) return mid; else if(s[mid]-k>m) check(ll,mid-1,k); else if(s[mid]-k<m) check(mid+1,rr,k); } main(){ cin>>m; s[0]=0; for(int i=1;i<=m;i++){ s[i]=s[i-1]+i; } for(int i=1;i<=m;i++){ /* for(int j=1;j<i;j++){ if(s[i]-s[j-1]==m){ cout<<j<<" "<<i<<endl; } }*/ int k=check(i+1,m,s[i-1]); if(k!=-1) cout<<i<<" "<<k<<endl; } return 0; }
法二:考虑数学方法。
在洛谷标签上,我们可以发现一个标签:数学,数论。那么这就代表了数学解法。观察这道题,我们发现,这些数字的差值都是一样的,这其实是个等差数列。
对于每个等差数列,我们有个等差数列求和公式,直接带入这个公式就可以A了这个题。
Code详见Luogu题解。