首页 > 试题广场 >

万万没想到之抓捕孔连顺

[编程题]万万没想到之抓捕孔连顺
  • 热度指数:57684 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 128M,其他语言256M
  • 算法知识视频讲解
我叫王大锤,是一名特工。我刚刚接到任务:在字节跳动大街进行埋伏,抓捕恐怖分子孔连顺。和我一起行动的还有另外两名特工,我提议

1. 我们在字节跳动大街的 N 个建筑中选定 3 个埋伏地点。
2. 为了相互照应,我们决定相距最远的两名特工间的距离不超过 D 。

我特喵是个天才! 经过精密的计算,我们从X种可行的埋伏方案中选择了一种。这个方案万无一失,颤抖吧,孔连顺!
……
万万没想到,计划还是失败了,孔连顺化妆成小龙女,混在cosplay的队伍中逃出了字节跳动大街。只怪他的伪装太成功了,就是杨过本人来了也发现不了的!

请听题:给定 N(可选作为埋伏点的建筑物数)、 D(相距最远的两名特工间的距离的最大值)以及可选建筑的坐标,计算在这次行动中,大锤的小队有多少种埋伏选择。
注意:
1. 两个特工不能埋伏在同一地点
2. 三个特工是等价的:即同样的位置组合( A , B , C ) 只算一种埋伏方法,不能因“特工之间互换位置”而重复使用


数据范围:

输入描述:
第一行包含空格分隔的两个数字 N和D(1 ≤ N ≤ 1000000; 1 ≤ D ≤ 1000000)

第二行包含N个建筑物的的位置,每个位置用一个整数(取值区间为[0, 1000000])表示,从小到大排列(将字节跳动大街看做一条数轴)


输出描述:
一个数字,表示不同埋伏方案的数量。结果可能溢出,请对 99997867 取模
示例1

输入

4 3
1 2 3 4

输出

4

说明

可选方案 (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)   
示例2

输入

5 19
1 10 20 30 50

输出

1

说明

可选方案 (1, 10, 20)   
示例3

输入

2 100
1 102

输出

0

说明

无可选方案  
枚举左端点。
像单调栈一样寻找右边最远的可满足的节点坐标。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    final static int MOD = 99997867;
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt(); // 建筑物数量。
        int D = in.nextInt(); // 最大距离。
        List<Integer>  building = new ArrayList<>();
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            building.add(in.nextInt());
        }
        calNum(N, D, building);
    }
    // 枚举左边的位置 : md 看错题了, 我还以为是,相邻不能超过D
    static void calNum(int N, int D, List<Integer> building) {
        long[] right = new long[N];
        Arrays.fill (right, N-1);
        Queue<Integer> queue = new LinkedList<>();
        queue.add(0);
        for (int i = 1; i < N; i ++) {
            while (!queue.isEmpty() && (building.get(i) - building.get(queue.peek())) > D) {
                right[queue.poll()] = i-1; // 右侧最远距离。
            }
            queue.add(i);
        }
        long res = 0;
        for (int i = 0; i < N; i++) {
            long n = right[i] - i;
            if (n >= 2) {
                res +=  (n*n-n)/2;
                res %= MOD;
            }
        }
        System.out.println(res);
    }
}


发表于 2023-12-20 19:48:30 回复(0)
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n, d;
        long res = 0;
        ArrayList<Integer> pos = new ArrayList<Integer>();
        n = sc.nextInt();
        d = sc.nextInt();
        for (int i = 0; i < n; i++) {
            pos.add(sc.nextInt());
        }
        for (int i = 0; i < n; i++) {
          //(1)
            int index = Collections.binarySearch(pos, pos.get(i) + d);
          //(2)
          if (index < 0) {
                index = -1 - index - 1;
            }
          //(3)
            if ((index - i) >= 2) {
                res = (res + (long) (index - i - 1) * (index - i) / 2) % 99997867;
            }
        }
        System.out.println(res);
    }
}

在这个问题中,我们需要从一组有序的正整数中选出三个数,使它们互相的差值小于等于给定的数d。如果我们从这些数中选出三个数,那么它们中间必然存在两个数的差值小于等于d,而我们需要计算所有符合这个条件的三元组个数。

我们可以从给定的有序数列中每个数开始,寻找符合条件的三元组个数。假设当前数的下标为i,我们需要在从i开始到n-1的范围内寻找最大的下标j,使得pos[j]-pos[i]<=d。然后我们可以计算出从i到j之间的符合条件的三元组个数。而在从i开始到j之间的三元组中,任意选取两个数的差值肯定小于等于d,因此我们只需要在这个区间中任意选取两个数,计算选法数目即可。而选取两个数的选法数目,就是从这个区间中选取两个数的组合数,即C_{2}^{j-i-1}。注:不包括i在内,所以是2

(1)这段代码使用 Collections.binarySearch() 方法在 pos 数组中查找第一个大于 pos.get(i) + d 的元素的位置,如果找到了则返回该位置的下标,否则返回负数,这个负数的值是插入该元素时应该放置的位置的相反数再减一。

(2)当在有序列表 pos 中找不到大于 pos.get(i) + d 的元素时,Collections.binarySearch() 方法返回负数值,而这段代码将这个负数值转换为对应的插入点(即若将 pos.get(i) + d 插入到 pos 中,应该插入的位置),然后将插入点再减1,得到第一个小于等于 pos.get(i) + d 的元素的下标 index。这个下标是用来计算符合要求的三元组数量的。

(3)这段代码是在计算符合条件的三元组数量,其中 (index - i) >=2的用来判断能否从以i为起点,找到至少3个元素其中index是通过二分查找得到的,表示在当前位置i之后第一个差值大于d的位置。

(index - i - 1) * (index - i) / 2的含义是从当前位置iindex-1之间取两个数的组合数,即从index - i - 1个数中选2个数的组合数。这里采用的是组合数公式:C_{m}^{n}=\frac{m!}{n!(m-n)!},即从n个数中选取m个数的组合数。在一组符合要求的数中,任意选取两个数将它们两者间的差值小于等于d的条件都满足,因此选取任意两个数都是合法的方案。

因此,(index - i - 1) * (index - i) / 2就等价于C_{index - i - 1}^{2},表示从index - i - 1个数中选2个数的组合数。

最后,由于可能出现比较大的结果,需要对结果取模,防止溢出。这里取模数为99997867


编辑于 2023-04-20 15:23:40 回复(0)
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String input1[] = in.nextLine().split(" ");
        int n =Integer.parseInt(input1[0]);
        int d =Integer.parseInt(input1[1]);
        input1 = in.nextLine().split(" ");
        //int arr[] = new int[input1.length];
        TreeMap<Integer,Integer> map  = new TreeMap<Integer, Integer>();
        //TreeSet<Integer> set = new TreeSet<>();
        for (int i=0;i<input1.length;i++){
            //arr[i]=Integer.parseInt(input1[i]);
            int num = Integer.parseInt(input1[i]);
            if (map.containsKey(num)){
                continue;
            }
            map.put(num,i);
        }
        long res= 0;
        Set<Integer> set = map.keySet();
        int begin = 0;
        for (Integer integer : set) {
            if (begin>=n-2){
                break;
            }
            int limit = integer+d;
            Integer floor = map.floorKey(limit);
            Integer index = map.get(floor);
            res+=((index-begin)*(index-begin-1))/2;
            res%=99997867;
            begin++;
        }
        System.out.println(res);
    }
}

为什么过不了最后两个用例啊?感觉写的没问题啊
发表于 2022-05-11 13:38:09 回复(1)
import java.util.*;

public class Main {
    
    private static long calC(long num) {
        return num * (num - 1) / 2;
    }

    public static void main(String[] args) {
        int mod = 99997867;
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt(), D = sc.nextInt();
        long cnt = 0;
        if (N <= 2) {
            System.out.println(-1);
            return;
        }
        int[] locs = new int[N];
        for (int i = 0; i < N; i++) {
            locs[i] = sc.nextInt();
        }
        sc.close();
        int left = 0, right = 2;
        while (right < N) {
            if (locs[right] - locs[left] > D) left++;
            else if (right - left < 2) right++;
            else {
                cnt += calC(right - left);
                right++;
            }
        }
        cnt %= mod;
        System.out.println(cnt);
    }
}

发表于 2022-02-10 10:18:56 回复(0)

考点:排列组合,同时利用“定一计算二”的方式即可,即总共只有三个数字去排列,所以可以将最右边的固定下,左边两位通过组合的计算公式就可以得出,当然左边两位的边界还需要进行一些判断

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        long num = in.nextInt();
        if(num<3){
            System.out.println(0);
            return;
        }
        int dis = in.nextInt();
        List arr = new ArrayList();
        int i=0;
        while(i++<num) {
            arr.add(in.nextInt());
        }
        int left = 0;
        int right = 2;
        long count = 0;

        while(right != num){
            int curDis = arr.get(right)-arr.get(left);
            if(curDis <= dis) {
                if(right-left > 1){
                    count+=C(right-left);
                    right++;
                } else{
                    right++;
                }

            }else if(curDis > dis){
                if(right-left > 1){
                    left++;
                } else{
                    break;
                }
            }
        }
        System.out.print(count%99997867L);

    }

    public static long C(long num){
        if(num == 2)return 1L;
        return num*(num-1)/2;
    }

}
发表于 2021-11-02 14:59:12 回复(0)
import java.util.*;

public class Main{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n, d;
        n = sc.nextInt();
        d = sc.nextInt();
        int[] a = new int[n+1];
        for (int i = 0 ; i < n; i++){
            a[i] = sc.nextInt();
        }
        // 从左到右会超时,因为右节点有回头,每一次都需要j重置到i+1的位置。
        // run_left(n, d, a);
        // 改用从右到左判断,i每次向后移,j必定在当前值的基础上进行移动。即以最右边的节点作为“定点”
        run_right(n, d, a);
    }
    
    public static void run_right(int n, int d, int[] a) {
        Long result = 0L;
        for (int i = 2, j = 0 ; i < n; i++){
            while(a[i] - a[j] > d) {
                // 因为即使 a[i] - a[i-1] >d, 当j==i 的时候,不满足 a[i] - a[j] > d 的条件
                // 所以这里不会使 j > i
                j++;
            }
            // 两者想乘可能会 超过 int 的返回,导致结果为负,输出的结果错误
            result += ((long)(i-j)*(long)(i-j-1)/2);
        }
        System.out.println(result % 99997867);
    }
    
    public static void run_left(int n, int d, int[] a) {
        Long result = 0L;
        for (int i = 0 ; i < n-2; i++){
            int j = i + 1;
            while(j+1 < n && a[j+1] - a[i] <= d) {
                j++;
            }
            result += ((j-i)*(j-i-1)/2);
        }
        System.out.println(result % 99997867);
    }
}

发表于 2021-02-26 15:18:55 回复(0)
import java.util.Scanner;
 public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int d = scanner.nextInt();
        int n=scanner.nextInt();
        int [] a=new int[d];
        for (int i = 0; i < d; i++) {
            a[i]=scanner.nextInt();
        }
        //定义计数器
        int count=0;
        //遍历,1号位定位
        for (int i = 0; i <d-2 ; i++) {
            //遍历,2号位定位
            for(int j=i+1;j<d-1;j++){
                //三号位,
                for (int k=j+1;k<d;k++){
                    if (a[k]-a[i]<=n){
                        count++;
                    }
                }
            }
        }
        System.out.println(count%99997867);
    }

发表于 2020-10-10 13:30:29 回复(0)
两种方法,方法一:二分搜索
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @Author: JiaJianpeng
 * @Date: 2020-05-06 9:08
 * @Description: com.t2019.字节跳动
 * @version: 1.0
 */
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line1[] = br.readLine().split(" ");
        int N = Integer.valueOf(line1[0]);
        int D = Integer.valueOf(line1[1]);
        String line2[] = br.readLine().split(" ");
        int loc[] = new int[N];
        for (int i = 0; i < N; i++)
            loc[i] = Integer.valueOf(line2[i]);
        long res = 0;
        for (int i = 0;i <= N-3; i++){
            res += bs(i, loc, N-1, D);
            if (res >= 99997867)
                res %= 99997867;
        }
        System.out.println(res);
    }

    private static long bs(int target, int[] loc, int r, int D) {
        int l = target + 2;
        int mid;
        long res;
        while (l <= r){
            mid = (l + r) >>1;
            if (loc[mid] - loc[target] <= D)
                l = mid + 1;
            else
                r = mid - 1;
        }
        res = r - target;
        return res >= 2? res*(res-1)/2: 0;
    }
}

方法二:滑动窗口
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
 
/**
 * @Date: 2020-05-06 10:07
 * @version: 1.0
 */
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line1[] = br.readLine().split(" ");
        int N = Integer.valueOf(line1[0]);
        int D = Integer.valueOf(line1[1]);
        String line2[] = br.readLine().split(" ");
        int loc[] = new int[N];
        for (int i = 0; i < N; i++)
            loc[i] = Integer.valueOf(line2[i]);
        long count = 0L;
        int right = 2;
        for(int i = 0; i< N-2; i++){
            long temp=0L;
            int j = right;
            for(; j < N && loc[j] - loc[i] <= D; j++);
            temp = j - 1 - i;
            if(temp >= 2)
                count += temp * (temp-1) / 2;
            if (count >= 99997867)
                count %= 99997867;
            right = j;
        }
        System.out.println(count);
    }
}
两种方法耗时差不多。。。,但坐标位置均匀分布的话,可以初次查找用二分,后面再用滑动窗口,这样耗时应该比较少。

编辑于 2020-05-06 10:52:29 回复(1)
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int N = input.nextInt();
        int D = input.nextInt();
        int[] L = new int[N];
        for(int i = 0; i < N; i ++)
        	L[i] = input.nextInt();
        input.close();
        System.out.println(Solution(N, D, L));
    }
    
    public static long Solution(int N, int D, int[] L) {
    	final int MOD = 99997867;
    	long sum = 0;
    	int i = 0;
    	int j = 2;
    	while(j < N) {
    		if(L[j] - L[i] > D)
    			i ++;
    		else if(j - i < 2)
    			j ++;
    		else {
    			sum += Sum(j-i-1);
    			j ++;
    		} 		
    	}
    	return sum%MOD;  	
    }
    
    public static long Sum(long n) {
		return (1+n)*n/2;   	
    }
}

发表于 2019-09-06 14:51:46 回复(1)