首页 > 试题广场 >

数对

[编程题]数对
  • 热度指数:58600 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解

牛牛以前在老师那里得到了一个正整数数对(x, y), 牛牛忘记他们具体是多少了。

但是牛牛记得老师告诉过他x和y均不大于n, 并且x除以y的余数大于等于k。

牛牛希望你能帮他计算一共有多少个可能的数对。


输入描述:
输入包括两个正整数n,k(1 <= n <= 10^5, 0 <= k <= n - 1)。


输出描述:
对于每个测试用例, 输出一个正整数表示可能的数对数量。
示例1

输入

5 2

输出

7

说明

满足条件的数对有(2,3),(2,4),(2,5),(3,4),(3,5),(4,5),(5,3)
// 时间复杂度O(N)
// 将除数y从k+1 开始计算,除数为y时,数对的个数包括两部分: n/y*(y-k) 和多出来的另一部分,这部分需要看
// n%y 和k的大小关系 
import java.util.*;
public class Main{
    public static void main(String[] arsg){
        Scanner sc= new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        long sum = 0;
        int t = 0;
        int tt = 0;
        for(int i=k+1;i<=n;i++){
            t++;
            tt = (n%i - k + 1) >0 ? (n%i - k + 1):0;
            sum+=n/i*t+tt;
        }
        if(k == 0) sum-=n;// k=0 特殊情况  多计算了n次
        System.out.print(sum);
    }
}

发表于 2018-03-28 15:55:45 回复(8)
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        Long count=0l;
        long n=scanner.nextInt();
        long k=scanner.nextInt();
        /*暴力解决时间复杂度过高
         * for(int y=k;y<=n;y++){
            for(int x=k;x<=n;x++){
                if(x%y>=k)
                    count++;
            }
        }*/
        if(k==0){
            count=n*n;
        }else{
            //余数要大于k,除数一定是从k+1开始的。
            for(long y=k+1;y<=n;y++){
                //余数是从0到y-1循环的,
                //对于每个y值,x从1到n包含n/y个余数循环,
                //每个余数循环中只有y-k个符合条件的。
                count+=(n/y)*(y-k);
                //剩下一个不完整的余数循环,判断这部分最大的余数是否大于k
                if(n%y>=k)
                    count+=n%y-k+1;
            }
            
            
        }
        System.out.println(count);
        
    }

}

发表于 2018-05-07 11:25:05 回复(0)
"""
对于(x,y),x和y均不大于n, 并且x除以y的余数大于等于k
y从k+1到n遍历:
x的个数 = (n // y) * (y - k) + max((n % y) - k + 1, 0)
求和即所得

by the way:考虑特例 k=0时,y应从2开始遍历
测试用例中 0%1 计算在内
其他解答中 k=0时,直接输出n*n是没有依据的
"""
import sys

if __name__ == "__main__":
    # sys.stdin = open('input.txt', 'r')
    n, k = list(map(int, input().strip().split()))
    ans = 0
    for y in range(max(2,k + 1), n + 1):
        ans += (n // y) * (y - k) + max((n % y) - k + 1, 0)
    if k==0:
        ans += 1  #测试用例中 0%1 计算在内
    print(ans)

编辑于 2019-07-02 11:46:56 回复(0)
链接:https://www.nowcoder.com/questionTerminal/bac5a2372e204b2ab04cc437db76dc4f?toCommentId=6246748
来源:牛客网
如果k = 0, 直接print(n*n)
否则,列一个n*n的余数表会发现每一行都会有一个循环的关系。
大于等于K的数都在K+1行及以后。
初始i = 1,每过一行i+=1
发现第K+i 行满足大于等于K的一共
1): n/(k+i)没有余数:(n//(k+i))*i
2):n//(k+i)有余数:(n//(k+i))*i+(/n%(k+i)-k+1)
n,k = map(int,input().split())
i = 1
count = 0
if k == 0:
    print(n*n)
else:
    while k+i <= n:
        a = (n//(k+i))*i
        b = n%(k+i)
        if b < k:
            count+=a 
        if b>=k: 
            count+=(a+b-k+1)
        i+=1
    print(count)


发表于 2020-06-01 14:48:28 回复(0)
从k+1开始(因为y<=k时不存在合法的x)枚举y,把n划分成n/y段,每段长度都为y(最后一段特殊处理),那么每段的k,k+1,k+2.....y-1位置都是合法的x点,故每段贡献y-k个答案。
#include <bits/stdc++.h>
using namespace std;
int main(){
    int n,k;
    long long ans = 0,cnt,res;
    scanf("%d %d",&n,&k);
    for(int i=k+1;i<=n;i++){
        cnt = n/i;  ///前面划分了cnt段长度为i的区间
        ans += cnt*(i-k); ///每段贡献i-k     
        if(n%i==0)continue;    
        res = n-cnt*i; ///最后一段长度
        if(k==0)ans += res;  ///k=0时特殊处理最后一段
        else if(res>0&&res>=k)ans += res-k+1; ///当最后一段长度>=k时,贡献的答案
    }
    printf("%lld\n",ans);
    return 0;
}

编辑于 2019-06-28 10:21:47 回复(0)
如果两次循环会产生n*n次计算
因此还需要发现其中的规律
从y入手,我们发现有以下规律:
y<k+1时,x/y的余数均小于k,因此有的取值:[k+1,n]
如n=13,k=3时:
y=[4,13],如当y=5时,符合条件的有
1 [3,5]
1 [4,5]
2 [8,5]
2 [9,5]
3 [13,5]
 我们将上述结果分两种来计算:
1)在n/y=2次循环内,x=[0-5]和x=[6-10]中分别有y-k个是余数>=k的,因此是n/y*(y-k)
2)在2次循环外,x=[11-13],x有n%y=3种取值,其中有n%y-k+1个余数>=k;如果没有余数>=k的则count=0
因此,k!=0时,count=n/y*(y-k)+max(0,n%y-k+1)
#coding:utf-8 import sys
line=sys.stdin.readline().split()
n=int(line[0])
k=int(line[1])
count=0 if k==0:
    count=n*n else:  for y in range(k+1,n+1):
        count+=n/y*(y-k)+max(0,n%y-k+1) print count

编辑于 2018-06-26 15:53:44 回复(0)
importjava.util.Scanner;
 
publicclassMain {
    publicstaticvoidmain(String[] args) {
        Scanner sc = newScanner(System.in);
        while(sc.hasNext()) {
            intn = sc.nextInt();
            intk = sc.nextInt();
            longnlong = (long)n;
            longklong = (long)k;
            longtimes = nlong - klong;
            if(klong == 0L){
                times = times - 1L;
            }
            longallNum = (times * (times + 1)) / 2;
            for(longy = klong + 1L; y <= nlong; y++){
                allNum += (nlong / y - 1L) * (y - klong);
                longremine = (nlong % y) - klong + 1L;
                if(remine >= 0L){
                    allNum +=  remine;
                }
            }
            System.out.println(allNum);
        }
        sc.close();
    }
}

编辑于 2018-03-28 11:31:33 回复(0)
JavaScript(Node) 😎题目:网易-数对
// 时间复杂度O(N)
// y =>[k+1,n] res=n/y*(y-k)
// n%i-k>=0 ? res+n%i-k+1:res
const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
})
let res = 0
rl.on('line',line =>{
    let n = +line.split(' ')[0],
        k = +line.split(' ')[1]
    if(k === 0){
        res = n*n
        console.log(res)
        return
    }
    //x=[1,n] y=[k,n] 遍历y,满足条件x的总数为(n/y)*(y-k)
    for (let i = k+1; i <= n; i++) {
        let cnt = Math.floor(n/i)
        let restLen = n%i
        res += cnt * (i-k)
        restLen >=k ? res += restLen-k+1: res
    }
  console.log(res)
})


发表于 2020-02-25 19:07:32 回复(0)
#include <cstdio>

#define ll long long

int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    ll res = 0;
    //x=p*y+r,r>=k
    //因为除数>余数,即y>r,所以y>k
    //枚举y,因为1<=x<=n,当给定y时,我们先求出最大的p:pMax
    //那么当p取[0,pMax-1]时,r能取到[k,y-1]的所有值
    //当p取pMax时,需要看当前余数r比k大的数量(r-k+1)
    for(int y=k+1;y<=n;y++){
        //pMax>=1
        int pMax = n/y;
        int r = n - pMax*y;
        //当p取到[0,pMax-1]时,r可以取[k,y-1],共pMax*(y-k)个
        res+=(ll)pMax*(y-k);
        //注意,当p=0时,x=r>=k,若k==0,那么x可以取到0,这不符合题目中x为正整数的要求,需要去掉
        if(!k) res--;
        //当p=pMax时,只能取[k,r]共(r-k+1)个数
        if(r>=k) res+=(r-k)+1;

    }
    printf("%lld\n",res);
    return 0;
}

以上为包含了具体分析的版本,因为“!k”那行判断代码只在k==0时执行,因此我们可以单独处理k==0时的例子,代码如下:
#include <cstdio>

#define ll long long

int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    if(k==0) printf("%lld\n",(ll)n*n);
    else{
        ll res = 0;
        for(int y=k+1;y<=n;y++){
            int pMax = n/y;
            int r = n - pMax*y;
            res+=(ll)pMax*(y-k);
            if(r>=k) res+=(r-k)+1;

        }
        printf("%lld\n",res);
    }
    return 0;
}


发表于 2020-02-14 18:21:33 回复(0)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n, k;
    while(cin>>n>>k){
        if(k==0){
            cout<<long(n)*long(n)<<endl;
            continue;
        }
        long cnt = 0;
        for(int y=k+1;y<=n;y++)
            cnt += (n/y)*(y-k) + max(n%y-k+1, 0);
        cout<<cnt<<endl;  
    }          
    return 0;
}

发表于 2019-08-11 20:26:27 回复(0)
JAVA解答:首先感谢大佬的解题思路。数学还是很重要啊2333333
import java.util.Scanner;

public class Main {
    public static void main(String[] args){
        Scanner input = new Scanner(System.in);
        long n = input.nextInt();
        long k = input.nextInt();
        long count = 0;
        if (k==0)
            System.out.println(n*n);
        else {
            for (long i = k+1;i<=n;i++){
                //+号前是分成一个一个小间隔,将每个小间隔里满足条件的个数乘以小间隔个数。
                //+号后求的是最后一个小间隔里面是否有满足条件的。
                count += (n/i)*(i-k) + Math.max(0,n%i-k+1);
            }
            System.out.println(count);
        }
    }
}

编辑于 2019-08-04 21:51:15 回复(0)
这个题没有问题吗?0除以任何非0的数都是0啊,所以k=0的时候都要加上n.
发表于 2019-07-04 11:53:37 回复(1)
蛮多细节的,首先是k=0的情况下,所有的数字都是可以的。
然后k!=0时,枚举y, 如果此时 y<=k ,那么任何数模k都不会大于等于 k; 如果 x<y,那么合法的 x ∈ [y-k,y-1], 如果 x >y ,那么合法的x必然是 y的倍数+k,即 x∈ [y*i+k, y*(i+1)-1] ,注意区间不能超过给定的范围 n。
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n,k;
    long long ans = 0;
    scanf("%d%d",&n,&k);
    if(k==0) ans = 1LL*n*n;
    else{
        for(int y=1;y<=n;y++){
            if(k>=y) continue;
            ans += y-k;
            for(int j=1;;j++){
                if(y*j+k>n) break;
                int l = y*j+k;
                int r = min(y*(j+1)-1,n);
                ans += r - l+1;
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}


发表于 2019-06-28 10:34:27 回复(0)
/*
要用long。
*/
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        long n = in.nextInt();
        long k = in.nextInt();
        long ans = 0;
        long cir = 0;   //出现的循环数
        long lef = 0;   //出现的余数
        long cnt = 0;   //每次循环出现的数对个数
        if (k == 0) {
            ans = n * n;
        } else {
            for (long i = k + 1; i <= n; i++) {
                cir = n / i;
                lef = n % i;
                cnt = i - k;
                ans += cir * cnt;
                if (lef >= k)
                    ans += lef - k + 1;
            }
        }
        System.out.println(ans);
    }
}

发表于 2019-06-26 11:13:44 回复(0)
n, k = list(map(int, input().split()))

count = 0
for y in range(k+1, n+1):
    if k:
        c = n//y*(y-k)
        if n%y>=k:
            c+=n%y-k+1
    else:
        c = n
    count += c
    
print(count)

发表于 2018-11-21 09:54:27 回复(0)
/**
 *运行时间:37ms
 *思路就是对暴力方法做优化
 *1. 要满足y <= n,并且x % y >= k,就要保证y的范围在[k + 1, n],构成第1层循环
 *2. 同样x <= n,并且x % y >= k,就要保证y的范围在[k, n],构成第2层循环
 * 不过第2层的优化在于只有每一层循环只有y-k个数对满足条件
 * 
 */

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int n = in.nextInt(), k = in.nextInt();


        if(k == 0) {
            System.out.println((long) n * n);
            return;
        }

        long sum = 0;
        for (int y = k + 1; y <= n; ++y){
            int d = y - k;
            for (int x = k; x <= n; x += y){
                sum += Math.min(n - x + 1, d);
            }
        }

        System.out.println(sum);
    }
}
编辑于 2018-03-28 12:20:49 回复(0)
朴素的做法是枚举n^2个点然后跟k作比较。这显然对n<=100000的规模来说是不允许通过的。
注意到当除数是y时,当x=1~n时,余数是1,2,3,...,y-1,0循环出现,循环节长度显然是y
那么我们可以枚举y=k~n(当y<k时所有余数均小于k,因此不需要考虑)
然后对于x=1~n,总共出现了[n/y]个循环节,然后数出每个循环节里面不小于k的余数。最后再数出不满一个循环节的不小于k的余数,就是答案了。注意当k=0的时候由于余数0出现在循环的末尾,因此要特别判断。
复杂度为O(n)
#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    long long n,k;
    cin>>n>>k;
    long long ans=0;
    for(int y=max(1LL,k);y<=n;++y)
    {
        int res=0;
        res=n/y*(y-k);
        if(n%y>=k)
            if(k)
                res+=n%y-k+1;
            else res+=n%y;
        ans+=res;
    }
    cout<<ans<<endl;
} 
发表于 2018-03-28 01:32:21 回复(13)
#include<stdio.h>
#define max(x,y) ((x)>(y)?(x):(y))
int main() {
  int n, k;  while (~scanf("%d%d", &n, &k)) { int y;  long long count = 0; for (y = k + 1; y <= n; ++y) { count += (n / y)*(y - k) + max(n%y+1-k, 0); if (!k) count--; } printf("%lld\n", count); } return 0;
}

x可以在 [1, n] 上取,但是y只能在 [k, n]上取,因为k以下都不存在大于等于k的余数。
所以遍历y,对于每一个y,统计符合的x的个数,加到count里。

先假设x可以从 [0, n]中取值,那么这段区间至少可以分成(n/k)个完整的、长度为y的区间。
x = 【0,1……y-2,y-1】【y,y+1,……,2y-2,2y-1】……【……】……【……,n】
在每个小区间a上,第i个数a[i]%y的余数是i。这样每一小段上大于等于k的x有y-k个(显然当k=0时,y个数都满足题意)。
【0,1,……,k,k+1,……,y-1

这样,已经遍历的总数是(n/y)*y,而其中满足条件的x的总数是(n/y)*(y-k)
因为n = (n/y)*y + n%y
所以还没遍历的数有 n+1-(n/y)*y = n+1 - (n - n%y) = 1+n%y 个,令它为t。
因为n%y∈[0, y-1], 则t∈[1, y]。

也就是说我这种方法,至少剩了一个数,至多会把一个整区间(数量为y)都剩下来。
但是无论如何,这个区间last的第i个数last[i]%y一定是i。则最后一个数(n)的余数就是n%y。
如此一来,此区间内从 [k,n%y] 包含共计 n%y-k+1 个数。不过如果算出小于0的数,则不需要减回去,直接当没有就可以了。
所以最后一个区间里包含了 max(n%y-k+1, 0) 满足条件的x。

最后注意,这里实际上枚举了x∈ [0, n]所有的数字,当k==0的时候,多统计了一个0,必须减掉。
编辑于 2018-04-04 20:52:12 回复(8)
import java.util.*;

public class Main{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long n = sc.nextInt();
        long k = sc.nextInt();
        long ans = 0L;
        if (k == 0) {
            ans = n * n; // 任何两对数的余数都是大于等于零
        } else {
            for (long y = k + 1; y <= n; y++) {
                ans += (n / y) * (y - k) + Math.max(0, n % y - k + 1);
                // 假设n=10,k=3,则对y来说只能是4,5,6,7,8,9,10
                // 当y=4,(n/y)*(y-k)代表x小于等于8(8是4的整数倍)时有(3,4),(7,4),Math.max(0,n%y-k+1)代表x大于8时符合题意的对数为0
                // 当y=5,(n/y)*(y-k)代表x小于等于10(10是5的整数倍)时有(3,5),(4,5),(8,5),(9,5),Math.max(0,n%y-k+1)代表x大于10时符合题意的对数为0
                // 当y=6,(n/y)*(y-k)代表x小于等于6时有(3,6),(4,6),(5,6),Math.max(0,n%y-k+1)代表x大于6时符合题意的对数为2,分别是(9,6),(10,6)
                // 当y=7,(n/y)*(y-k)代表x小于等于7时有(3,7),(4,7),(5,7),(6,7),Math.max(0,n%y-k+1)代表x大于7时符合题意的对数为1,是(10,7)
                // ...以此类推
            }
        }
        System.out.println(ans);
    }
}
发表于 2018-03-28 18:34:12 回复(5)
余数 1 2 3 4 5 6 7 8 9 10 11 12 ...
1 0 1 1 1 1 1 1 1 1 1 1 1 ...
2 0 0 2 2 2 2 2 2 2 2 2 2 ...
3 0 1 0 3 3 3 3 3 3 3 3 3 ...
4 0 0 1 0 4 4 4 4 4 4 4 4 ...
5 0 1 2 1 0 5 5 5 5 5 5 5 ...
6 0 0 0 2 1 0 6 6 6 6 6 6 ...
7 0 1 1 3 2 1 0 7 7 7 7 7 ...
8 0 0 2 0 3 2 1 0 8 8 8 8 ...
9 0 1 0 1 4 3 2 1 0 9 9 9 ...
10 0 0 1 2 0 4 3 2 1 0 10 10 ...
11 0 1 2 3 1 5 4 3 2 1 0 11 ...
12 0 0 0 0 2 0 5 4 3 2 1 0 ...
... ... ... ... ... ... ... ... ... ... ... ... ... ...

第一行和第一列表示索引】,上面余数矩阵(除去第一行第一列)元素Ai,j表示i%j,即取余的结果,可发现每一列都是不断循环重复的!!!!
temp = list(map(int, input().split()))
n = temp[0]
k = temp[1]
if k == 0:
    ans = n * n  # k=0是比较特殊的
else:
    ans = 0
    for y in range(k + 1, n + 1):
        # 从每一列来看,根据每y个一个循环的规律,快速计算余数矩阵余数值
        # cycle = [i for i in range(1, y)] + [0]  # 循环部分
        satisfy_num = y - k  # 一个循环中满足的组合个数
        cycle_num = n // y  # 完整循环个数
        res_num = n % y  # 剩余不完整部分循环中的元素个数
        ans += satisfy_num * cycle_num + max(res_num - k + 1, 0)  # 注意这里最差就是不完整部分满足个数为0,但是不能为负数
print(ans)
编辑于 2019-06-22 22:08:47 回复(0)