牛客编程题 WY49 数对

 链接:   WY49 数对  

如果需要查看最优答案,请移动到最下面  标题三、以Y为基底处理数对 

与其叫这道题为编程题,不如叫他数学题,需要依靠数学去找规律然后计算。因为它给到的测试用例暴力是求不出来的。

一、暴力求解

下面是暴力求解的代码

#include<stdio.h>
int main()
{
    int n,k,count=0;
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i%j>=k) 
                count++;
        }
    }
    printf("%d",count);
    return 0;
}

他的测试用例是这样,count会是一个非常大的值,因此暴力求解(n²)的时间复杂度是通不过的,后面我开始尝试通过正整数数对(x,y)的x为基底来找规律。

二、以X为基底处理数对

对于这个案例,n = 5, k = 2;我们可以发现当x>=k(k为2,x可以为2,3,4)的时候,y=x+1;在这个情况下,他们的余数永远大于k,因为x%k==x (x比k小,除不尽)。

我们发现x=2时,y=(3,4,5)满足   有三个

              x=3时,y=(4,5)   满足   有两个

              x=4时,y=(5)     满足   有一个

              x=5时,y不能大于n(5)因此没有

可以用高斯求和的方法来算出   count = (n-k)*(n-k+1)/2;来算出以上情况,我们是的后面的循环可以少一些

同时,可以把循环里i和j也略微处理一下(这一段略微推导一下就能懂),i必须要大于k*2  才能保证去模以(%)比他小的数 剩下的那个余数大于k(举例子:k=2的时候 i至少要为5(k*2+1),才可能i%j>=k),j最小也得是k+1,并且j最大只能为i-k(举例子:k=2,i=5时,j不能大于3,因为大于了   3(就是i-k)的时候,i%j的值就会小于k)。

形成了以下代码

#include<stdio.h>
int main()
{
    int n,k,count=0;
    scanf("%d %d",&n,&k);
    count = (n - k) * (n - k + 1) / 2;
    for(int i=k*2+1;i<=n;i++){
        for(int j=k+1;j<=i-k;j++){
            if(i%j>=k) 
                count++;
        }
    }
    printf("%d",count);
    return 0;
}

但是,我们只是略微出手,下手不够狠,对于时间复杂度只改变了一点点,最坏情况下依然是n²,还需要再操作一下。

我又想到一种情景:  举例子  n = 10, k = 3

通过上面的推导 遍历的i是从k*2+1开始的。

当 i = 7时 ,  j  =  4满足     有1个

    i = 8 时 , j   =  5满足    有1个

    i = 9 时  , j  =  5,6满足  有2个

    i  =  10时 j  =  6,7满足  有2个

我们发现这又是一个高斯求和的案例,但是需要处理n-k*2为单数还是双数,推导n-k*2为双数时

count+=((n-k*2)/2+1)*(n-k*2)/2;

n-k*2为单数

count += ((n-k*2)/2+1)*((n-k*2)/2)+(n-k*2)/2+1;

这里看起来很奇怪,双数竟然比单数还要多一个数,其实是合理的,因为单数/2会有余数,而双数/2刚刚好。

到此我们在处理一下  j  ,我们发现上面的j是有很明显的特征就是大于了 i/2 ,因此我们在把之前j的条件并上新条件   j<=i/2   变成了 j<=i-k&&j<=i/2 

#include<stdio.h>
int main()
{
    int, k;
    scanf("%d %d",&n,&k);
    int count = (n-k)*(n-k+1)/2 ;
    if((n-2*k)%2==1)
    {
        count += ((n-k*2)/2+1)*((n-k*2)/2)+(n-k*2)/2+1;
    }
    else {
        count+=((n-k*2)/2+1)*(n-k*2)/2;
    }
    for(int i=k+1;i<=n;i++)
    {
        for(int j=k+1;(j<=i-k&&j<=i/2);j++)
        {
            if(i%j>=k)
                count++;
        }
    }
    printf("%d",count);
}

但是还是老问题,治标不治本,这个值太大了,接近于n²的算法还是处理不了。也许后续还可以继续找规律处理值,但是我实在是处理不动了,我选择换一个思路。

三、以Y为基底处理数对

还是举个例子

输入(n = 10  , k = 3)

y是被除数,y一定要大于k+1,那么余数才会大于等于k

因此我们找规律:

y = 4 时            x = (3,7)                                                                     有两个

y = 5 时            x = (3,4,8,9)                                                               有四个

y = 6 时            x = (3,4,5,9,10)      11大于了n  排除                          有五个(排除1个)

y = 7 时            x = (3,4,5, 6,10)     11,12,13大于了n  排除                有五个(排除3个)

..........

y = 10时           x = (3,4,5,6,7,8,9)                                                      有七个

我们可以把y来切段,

(0--y)是第一组      (y+1--2y)是第二组       (2y+1--3y)是第三组  以此类推

可以发现每一组都有y-k个符合条件,只是需要看最后一组中是否有   x大于了n  这种不满足的。通过这种方法,我们可以在O(n)的时间复杂度下求出count,只需要一次遍历就可以,大大提升了效率。

 我们看  n % y  (即10%y)余数是否大于等于k

y = 4 时            10%y==2<k         x = (3,7)                                      有两个

y = 5 时            10%y==0<k         x = (3,4,8,9)                                有四个

y = 6 时            10%y==4>=k       x = (3,4,5,9,10)                           有五个(排除1个)

y = 7 时            10%y==3>=k       x = (3,4,5, 6,10)                          有五个(排除3个)

..........

y = 10时           10%y==0<k         x = (3,4,5,6,7,8,9)                        有七个

我们只需处理一下y>=k的时候,即可求出每一个y下count需要+多少。

下面是核心代码

int ret = n%y>=k?(y-k)*(n/y)+(n%y)-k+1:(y-k)*(n/y);

上面代码中(y-k)代表每一组有多少个,(n/y)代表有多少组,如果n%y>=k(即最后一组还有数据),则还需加上(n%y)-k+1

其中(n%y)-k+1,可能会有点懵逼怎么来的,继续整,还是上面这个例子

y = 6 时            10%y==4>=k     

这证明一组有6个数据,因为k=3,因此每一组前面两个数据是不符合条件的,后面的才符合条件, (n%y)-(k-1),即可算出最后一组后续有多少个合法数据。

再处理一下特殊条件 k=0 的时候输出n*n即可

最后附上代码(由于数据很大,int是不能通过的,需要再64位系统下用long 或者32位系统用long long 才能通过,牛客应该是64位系统)

#include <stdio.h>

int main() {
    long n, k;
    scanf("%ld %ld",&n,&k);
    long count = 0;
    if(k==0)
    {
        printf("%ld",n*n);
        return 0;
    }
    for(int y = k+1;y<=n;y++)
    {
        int ret = n%y>=k?(y-k)*(n/y)+(n%y)-k+1:(y-k)*(n/y);
        count+=ret;
    }
    printf("%ld",count);
    return 0;
}

感谢观看!!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值