[2021.11.14]UPC-2021级新生个人训练赛第3场-19412 Problem F 求和问题

题目描述

给定1到n共n个数,你需要找出连续的几个数使得其和为m,显然这样的方案有很多,请将每种方案都输出,输出方法请参考样例。

输入

第一行两个整数n和m。

输出

输出有多行,每行表示一种合法的方案,采用闭区间的方式给出方案([a,b]表示a到b这几个连续的整数),当有多种方案时按照a由小到大输出。

样例输入 Copy

20 15

样例输出 Copy

[1,5]
[4,6]
[7,8]
[15,15]

提示

样例说明
1+2+3+4+5 = 15
4+5+6 = 15
7+8 = 15
15 = 15
【数据规模】 
对于30%的数据,n,m<=100。
对于60%的数据,n,m<=1000000。
对于100%的数据,n,m<=10^12。

题解:

        最直观暴力的方法就是双层循环,穷举出所有和等于M的连续区间,时间复杂度是O(n^2)然而对于 10^12次方的数据量,是不可能以O(n^2)复杂度的算法,在限定时间 1s内解完的。。

        对于一般情况来说,执行的指令达到10^8量级,在1s时间内就已经很悬了。

        所以~我们来看看其他方法:

先忽略最大值n的条件,直接看如何求出和为m的连续区间。

思路肯定要放在如何把m拆成多个连续数的和

如果要把m成一个数,不用说,m肯定还是m。我们自然得到了区间[M,M]。

如果要把m拆成两个数,变成区间[x,x+1]呢?

        首先要看看m能不能被拆成两个数,

          拆成两个数,自然想到要将m/2,

                如果m是偶数 则m/2为一个整数,m=m/2+m/2。

                        而我们要将m以m=x+(x+1)(x为整数) 的形式表示出来,然而在m是偶数的情况                             下,观察可以发现(方程无整数解?)这是不可能的。

                当m为奇数时,m/2=(m-1)/2+0.5;

                        m=(m-1)/2+(m-1)/2+1;可以发现,m可以以两个连续数的和表示出来!以区间表                           示就是[(m-1)/2,(m-1)/2+1];

        那如果要将m拆为三个数呢?

                我们直接m/3,若有整数解的话,将会时三个相同的数 x,x,x;我们从左边的x拿走一个1加                   到右边的x上,变为x-1 x x+1。是三个连续的数!

                所以,如果m能被3整除,则其一定能被分为三个连续的数的和。同时,反过来也可证明                   三个连续数的和必定会被3整除

        我们稍微步子迈大一点。

       

        如果要将m拆为(奇数x)个数?

                例如拆为5个数,设x=n/5,那所拆成的5个数可以用 x-2 x-1 x x+1 x+2 表示

        多次举例,归纳可得

        所以,m必须要被x整除,才可拆为x个数 (x为奇数),                                                                   其区间表示为[m/x-(x-1)/2,m/x-(x-1)/2]

        那么偶数个数呢?

                例如4个数!16/4=4  对于 4 4 4 4 这四个数来说,我们采取一些手段,在变化最小的情况下,让其变成连续的数。最左边的4拿走2 中间靠左的4拿走1 最右边的4加上1.

变为了 2 3 4 5 其和为14。我们总共从4 4 4 4里面拿走了2.

                  再看一下 5 5 5 5 如果要使其变成连续的数,发现我们也需要从5 5 5 5里面拿走2.

                多次举例发现。

        (x为偶数)      如果要将x个相同的数变为 x个连续数。则需用从x个相同的数中取走(x/2)

那么可发现,x个连续数的和与x个相同数的和有着关系:

                总结一下,发现m如果可以被分为x个连续的数的和。

                则(m+x/2)一定要可被x整除,而其拆出来的区间应该表示方法..()在下面的代码部分中。

于是 我们得到了如下规律

        如果要将m拆为(奇数x)个连续数的和,则m一定要可被x整除。

        如果要将m拆为(偶数x)个连续数的和,则(m+x/2)一定要可被x整除

有了上述规律发现后,再用代码实现就不成问题了 :)

同时要注意区间的端点值需要小于等于n,这个方法的时间复杂度为不会算

        //循环次数为1+2+3+……+i>=n时i的取值;可得到i^2-i=2n的表达式。

        当n十分十分大时,i可忽略不计,原式为i^2=2n。

        可得到时间复杂度应为O(n^1/2)?//

        如有纰漏,请多多指正!!

#include <cstdio>
int main(){
    int q=0;
    unsigned long long int n,m,i,s=0;
    unsigned long long int a[1000];    //因为题目要求a从小到大输出,所以开了两个数组存储端点
    unsigned long long int b[1000]; 
    scanf("%llu %llu",&n,&m);
    for(i=1;;i++){           //i为要将m拆成的数的个数。
        s+=i;
        if(i&1){
            if(m%i==0){
                if(m/i+i/2<=n){
                    a[q]=m/i-i/2;b[q++]=m/i+i/2;
                }

            }
        }
        else{
            if((m+i/2)%i==0){
                if((m+i/2)/i+i/2-1<=n){
                    a[q]=(m+i/2)/i-i/2;b[q++]=(m+i/2)/i+i/2-1;
                }
            }
        }
        if(s>=m)    //当1+2+3+...+i>=m时,肯定无法再将m拆为更多的数了。
            break;
    }
    while(q--){
        printf("[%llu,%llu]\n",a[q],b[q]);
    }
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值