查找数组的最大公约数(GCD),在最短时间内排除某些元素

时间:2015-01-08 19:24:21

标签: java performance algorithm

我正在做一个竞争性的编程问题,给你一组数字,然后是一定数量的查询。对于每个查询,您将获得2个整数,'a'和'b'。所以你应该输出数组中剩余元素的GCD(不包括a,b和它们之间的所有元素)。

例如,如果数组是:16,8,24,15,20,并且有2个查询(2,3)和(1,3),则输出1为:1,输出2为:5。 请注意,索引是基于1的。

这是我的代码,其中我用一个函数实现了基本思想,找到传递给它的数组的GCD。

public static void main(String args[]) throws IOException {

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int t = Integer.parseInt(br.readLine());

    while (t-- > 0) {   //This is the number of test cases
        String[] s1 = br.readLine().split(" ");
        int n = Integer.parseInt(s1[0]);          //Number of elements in array
        int q = Integer.parseInt(s1[1]);          //Number of queries

        String[] s2 = br.readLine().split(" ");
        int[] arr = new int[n];

        for (int i = 0; i < n; i++) {
            arr[i] = Integer.parseInt(s2[i]);
        }

        for (int i = 0; i < q; i++) {            //for each query
            String[] s3 = br.readLine().split(" ");
            int a = Integer.parseInt(s3[0]) - 1;
            int b = Integer.parseInt(s3[1]) - 1;

            int[] copy = new int[n - b + a - 1];     //this is so that the original array doesn't get messed up

            int index = 0;
            for (int j = 0; j < n; j++) {       //filing the array without the elements of the query
                if (j < a || j > b) {
                    copy[index] = arr[j];
                    index++;
                }
            }

            int fin = gcd(copy);
            System.out.println(fin);

        }

    }

}

private static int gcd(int a, int b) {
    while (b > 0) {
        int temp = b;
        b = a % b; // % is remainder
        a = temp;
    }
    return a;
}

private static int gcd(int[] input) {        //simple GCD calculator using the fact that GCD(a,b,c) === GCD((a,b),c)
    int result = input[0];
    for (int i = 1; i < input.length; i++)
        result = gcd(result, input[i]);
    return result;
}

问题是我在某些部分上获得了AC(10个中的6个),其余部分则是TLE。有人可以建议一个更好的方法来解决这个问题,因为我的方法看起来太慢了,几乎不可能再进一步优化了吗?

3 个答案:

答案 0 :(得分:2)

您可以为所有前缀和后缀预先计算gcd。每个查询都是前缀和后缀的联合,因此需要O(log MAX_A)时间来回答一个。这是我的代码:

import java.util.*;
import java.io.*;

public class Solution {

    static int gcd(int a, int b) {
        while (b != 0) {
            int t = a;
            a = b;
            b = t % b;
        }
        return a;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(
                new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(System.out);
        int tests = Integer.parseInt(br.readLine());
        for (int test = 0; test < tests; test++) {
            String line = br.readLine();
            String[] parts = line.split(" ");
            int n = Integer.parseInt(parts[0]);
            int q = Integer.parseInt(parts[1]);
            int[] a = new int[n];
            parts = br.readLine().split(" ");
            for (int i = 0; i < n; i++)
                a[i] = Integer.parseInt(parts[i]);
            int[] gcdPrefix = new int[n];
            int[] gcdSuffix = new int[n];
            for (int i = 0; i < n; i++) {
                gcdPrefix[i] = a[i];
                if (i > 0)
                    gcdPrefix[i] = gcd(gcdPrefix[i], gcdPrefix[i - 1]);
            }
            for (int i = n - 1; i >= 0; i--) {
                gcdSuffix[i] = a[i];
                if (i < n - 1)
                    gcdSuffix[i] = gcd(gcdSuffix[i], gcdSuffix[i + 1]);
            }
            for (int i = 0; i < q; i++) {
                parts = br.readLine().split(" ");
                int left = Integer.parseInt(parts[0]);
                int right = Integer.parseInt(parts[1]);
                left--;
                right--;
                int res = 0;
                if (left > 0)
                    res = gcd(res, gcdPrefix[left - 1]);
                if (right < n - 1)
                    res = gcd(res, gcdSuffix[right + 1]);
                out.println(res);
            }
        }
        out.flush();
    }
}

答案 1 :(得分:0)

“几乎不可能进一步优化”?算了吧:

  1. 添加相邻输入元素的计算GCD 的缓存,因此无需重新计算它们。例如,有一个表格可以保存input[i]input[j]的GCD。请注意,这不会超过原始输入的一半大小。
  2. 计算连续输入对的GDC(这样你可以利用#1)
  3. 这可以扩展到更大的群体,但需要更多的空间。

答案 2 :(得分:0)

这里至关重要的是,一组数字A的GCD等于A的任何分区的GCD的GCD。例如,

GCD(16, 8, 24, 15, 20) = GCD(GCD(16, 8), GCD(24, 15, 20))

我会通过构建一些类似树的结构来利用这个事实。让我们为GCD[i, j]i之间的索引元素集合的GCD写j。对于大小为n的给定输入,我会存储:

GCD[1, n]
GCD[1, n/2], GCD[n/2+1, n]
...
GCD[1, 2], GCD[2, 3] ... GCD[n-1, n]

也就是说,在树的每个级别,GCD的数量加倍,并且计算它们的集合的大小减半。请注意,您将以这种方式存储n-1个数字,因此您需要线性额外存储空间。从头到尾计算它们,您需要将n-1 GCD操作作为预处理。

对于查询,您需要组合GCD,以便省略两个查询索引。例如,让我们有A数组n = 8,我们会查询(2, 4)

  • 我们不能使用GCD[1, 8],因为我们需要排除2和4,所以我们在树中更深一层。
  • 我们无法使用GCD[1, 4],但我们可以使用GCD[5, 8],因为要排除的索引都不在其中。上半年我们需要更深入。
  • 我们无法使用GCD[1, 2],也无法使用GCD[3, 4],因此我们会更深入一层。
  • 我们只使用元素A[1]A[3]

我们现在需要计算GCD[5, 8]A[1]A[3]的GCD。对于查询,我们只需要进行2次GCD计算,而不是以天真的方式进行5次计算。

一般情况下,您将花费O(log n)时间搜索结构,每次查询需要O(log n)次GCD计算。