洛谷P1147 连续自然数和

洛谷-链接-P1147 连续自然数和

这道题啊挺有意思的~

解法1

首先最容易想到的肯定是暴力啦~枚举1->N/2,然后第二层循环枚举接下来的数字,sum累加,如果等于就输出,如果大于就退出(超出范围),如果小于继续循环。

//暴力 
#include<cstdio>
using namespace std;
int main(){
    int N;
    int sum = 0;
    
    scanf("%d",&N);
    for(register int i=1;i<=N/2;i++){
        
        sum = 0;
        int book;
        for(register int j=i;j<=N/2+1;j++){
            book=j;
            sum+=j;
            if(sum>=N)break;
        }
        if(sum==N)printf("%d %d\n",i,book);
    } 
    
    return 0;
}

时间复杂度为O(N^2)

解法2

接下来可以推公式啦!!

神犇高斯在小时候快速计算出1+2+3+4+...+99+100的答案;
解法是(其实不一样啊……但是差不多了……同一个思想):

1+99=100
2+98=100
3+97=100
..
49+51=100
也就是说,以50为中点,左边的1-49分别对应右边的51-99
一共49对和为100的搭配,49100 = 4900
再加上中点50和100,结果为5050
(其实高斯是 (1+100)=101,50
101=5050)
则可以归纳为

    左起点l,右起点r
    又m=[(l+r)/2],此时要向下取整
    但是真正计算时存在小数 所以m=(l+r-1)/2
    SUM.1 = (l+r-1)(m-l)+m+r
    把l=1和r=100代入进行计算试试
    然后开始化简
    也就是
    	SUM.1 = (l+r-1) [ (l+r-1)/2 - l ] + (l+r-1)/2 + r
    	2*SUM.1 = (l+r-1) (r-l-1) l+r-1 + 2r 
        2*SUM.1 = (l+r-1)(r-l) + 2r
        2*SUM.1 = r(r+1)-l(l-1)
        
    但是如果m=(l+r)/2这一步不需要向下取整呢?
    如1-99 中点为50,而且49*100完后只加了50,而并不是像上面一样加了r
    
    所以需要再列一个情况
    当m=(l+r)/2 时,m为整数
    SUM.2 = (l+r) [ (l+r)/2 - l ] + (l+r)/2
    所以
      (l+r)(l+r-2l)+l+r=2*SUM.2
      (l+r)(r-l+1) = 2*SUM.2
      r(r+1)-l(l-1) = 2*SUM.2
     
     诶诶算到这里你发现跟上面的式子完全一样啊!!!
     那就方便很多了
     不需要分类讨论了~~~        

推导至此,结合题目,我们已知N,也就是推导过程中的SUM,也就可以算出2*SUM,就是2N

现在,我们只需要从小到大枚举l,不就可以从小到大算出每一组的r,然后输出了吗!!!

	r(r+1)-l(l-1)=2N
    r(r+1)=2N+l(l-1)

所以我们依次枚举l,然后使用二分去寻找r,判断寻找出的数是否满足以上等式即可


#include<cstdio>
using namespace std;

int N;
inline int bound(int l,long long x){
    int r=N/2+1;
    while(l<r){
        long long mid = (l+r)>>1;
        long long  num = mid*(mid+1);
        if(num==x)return mid;
        else if(num>x)r=mid-1;
        else l=mid+1;
    }
    if((long long)l*(l+1)==x)return l;
    return -1;
}

int main(){
    

    scanf("%d",&N);
    
    for(register int i=1;i<=N/2;i++){
        
        long long ard = (long long)i*(i-1)+2*N;
        int r = bound(i+1,ard);
        if(r!=-1)printf("%d %d\n",i,r);
    }
    return 0;
} 

时间复杂度应为O(NlogN)

哇感觉好快啊!!是不是可以AC了!!

当然可以啊!!!

但是

你会发现这个算法用了96ms,而暴力只用了26ms!!!!

岂不是白白优化了!!!!

那问题出在哪了!

其实O(N^2)的这个算法常数较小啊……随着循环变量i的增加,后面每次第二层循环的次数会越来越小,与真正的平方相差甚远。
而O(NlogN)这个算法,不仅枚举了N次,而且每次的二分都会增加次数,实打实的NlogN

但是你会发现啊……我们为什么要二分啊……为什么不能直接算啊!!!!

解法3

  r(r+1)=2N+l(l-1)
  使得2N+l(l-1)为X
  r(r+1)=X

你会发现X是已知的!!!!而且啊!左边的两个乘数只相差1!!非常近



那我们岂不是可以直接sqrt(X)就可以求出r了吗……向下取整一波,然后乘以(自己+1),看看是不是会等于X不就ok了

#include<cstdio>
#include<cmath>
using namespace std;
int N;
int main(){
    scanf("%d",&N);
    
    for(register int i=1;i<=N/2;i++){
        
        long long ard = (long long)i*(i-1)+2*N;
        long long r = floor( sqrt(ard) );//floor()向下取整 sqrt()开方
        if((long long)r*(r+1)==ard)  
            printf("%d %lld\n",i,r);
    }
    return 0;
} 

时间复杂度O(N)

洛谷时间8ms

哇好快啊!!

但是还有更快的!!

解法4

——此解法是学习题解的……

我们先看看上面的一个推导

但是如果m=(l+r)/2这一步不需要向下取整呢?
        如1-99 中点为50,而且49*100完后只加了50,而并不是像上面一样加了r
        
        所以需要再列一个情况
        当m=(l+r)/2 时,m为整数
        SUM.2 = (l+r) [ (l+r)/2 - l ] + (l+r)/2
        所以
          (l+r)(l+r-2l)+l+r=2*SUM.2
          (l+r)(r-l+1) = 2*SUM.2
          r(r+1)-l(l-1) = 2*SUM.2

再看其中的

          (l+r)(l+r-2l)+l+r=2*SUM.2
          (l+r)(r-l+1) = 2*SUM.2
          r(r+1)-l(l-1) = 2*SUM.2

再看

		(l+r)(r-l+1) = 2*SUM.2

转化成符合题意的

		(l+r)(r-l+1) = 2*N

使得(l+r)为a,(r-l+1)为b
则有ab=2N
显而易见
l = (a-b+1) / 2
r = (a+b-1) / 2
那我们是不是只需要枚举a,就可以算出b,然后算出l和r……

判断l和r是否满足(l+r)(r-l+1) = 2*N

如果满足就得到一个解!


#include<cstdio>
#include<cmath>
using namespace std;

int N;

int main(){
    
    scanf("%d",&N);

    int size =floor( sqrt( N<<1 ) );
    
    for(register int i=size;i>1;i--){
        int a = i;
        int b = (N<<1)/a;
        
        if(a<b){
            int t = a;
            a = b;
            b = t;
        }
        if(a*b!=(N<<1))continue;
        int l = (a-b+1) >> 1;

        int r = (a+b-1) >> 1;
        if((long long)r*(r+1)-(long long)l*(l-1)==N<<1)
            printf("%d %d\n",l,r); 
    }
    return 0;
}

时间复杂度为O(sqrt(N))

洛谷运行时间0ms

总结

从暴力循环到数学求解的过程令人收获满满~~~

然后做完题之后及时向别人学习更优解!!

反思自己为什么没有想到呢!

由于自己思维不够活跃

没有意识到这种做法

再以后的做题中要多角度想问题

例如换个东西循环,然后求出我们要的

posted @ 2018-02-25 21:58  Neworld1111  阅读(343)  评论(0编辑  收藏  举报