【问题标题】:Finding the greatest common divisor (GCD) of an array excluding some elements in minimum time在最短的时间内找到排除某些元素的数组的最大公约数 (GCD)
【发布时间】:2015-03-07 00:43:34
【问题描述】:

我正在做一个有竞争力的编程问题,给你一个数字数组,然后是一定数量的查询。对于每个查询,您将获得 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(十分之六),而在其余部分上获得了 TLE。有人可以提出更好的方法来解决这个问题,因为我的方法似乎太慢了,几乎不可能进一步优化?

【问题讨论】:

  • 对不起,我不明白你的例子是如何得到这些值的。对我来说 gcd(16,15,20) = 1gcd(8,15,20) = 1
  • @GáborBakos 真的很抱歉,我搞砸了。已更正。另外,我没有在那里提到 1 个关键点。请再次检查第一段。
  • GCD of an array的可能重复
  • @ILoveCoding 你的回答没有提供正确的解决方案(即使它被标记为如此),所以我想问另一个问题。
  • @pkm 它确实提供了正确的解决方案。是什么让您认为这是错误的?

标签: java performance algorithm


【解决方案1】:

您可以预先计算所有前缀和后缀的 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();
    }
}

【讨论】:

  • 兄弟,我已经提交了一个几乎相似的代码,得到了 TLE。为此,我按原样提交了此代码,即使 this 在完全相同的子任务中也遇到了 TLE 错误。
  • @pkm 是完全不同的代码。如果您正在谈论您在 cmets 中向我展示的与您的问题相关的链接,则它与此解决方案无关。
  • 我意识到这一点。之后我提交了另一个,现在这个。问题依然存在。
  • @pkm 您是否尝试使用PrintWriter 来加快输出速度?
  • 哦。最后。谢谢你的提醒,你猜怎么着?有效。谢谢你的帮助伙计。如果我早些时候在 cmets 中听起来太粗鲁了,我很抱歉。
【解决方案2】:

“几乎不可能进一步优化”?普肖:

  1. 添加计算的 GCD 的缓存相邻输入元素,因此它们不需要重新计算。例如,有一个包含input[i]input[j] 的GCD 的表。请注意,这不会超过原始输入大小的一半。
  2. 计算连续输入对的 GDC(以便您可以利用 #1)

这可以扩展到更大的组,但需要更多空间。

【讨论】:

  • 我认为通常gcd 是如此之快(至少对于小数字而言),缓存非局部性可能比重新计算值更有害。
  • 只要它足够加快慢速案件的速度,而又不会过多地减慢快速案件的速度,我们就有了赢家。
  • @ScottHunter 我忘了提,但是 n(数组大小)的最大值是 10^6。因此,我可以放心地假设存储所有对将占用比提供给我的空间更多的空间。 (JBTW 这个问题不包括 a & b 以及 a & b 之间的所有元素。我也忘了提及)
【解决方案3】:

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

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

我会通过构建一些树状结构来利用这一事实。让我们为索引在ij 之间的元素集的GCD 写GCD[i, 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 组合在一起,以便完全省略两个查询索引。例如,让我们有一个数组An = 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 计算。

【讨论】:

  • 是的,它是正确的,但是对于这个问题来说它是一个矫枉过正的问题。我们只对前缀和后缀的联合感兴趣,因此我们可以预先计算所有前缀和后缀的 gcd,并为每个查询执行 1 个 gcd 计算。
  • @ILoveCoding 有两个排除索引,对吧?所以也应该有一些中缀?
  • 我们确实需要前缀和后缀 GCD 的并集,但是计算它们本身需要很多时间,因为我们每次都会遍历整个数组。
  • 不,整个[a, b] 范围被排除在外。所以没有中缀。
  • @VincentvanderWeele 你没有误读这个问题。从那以后有更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-28
  • 1970-01-01
  • 1970-01-01
  • 2021-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多