链接: 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;
}
感谢观看!!!!