【问题标题】:Reducing Integer Fractions Algorithm减少整数分数算法
【发布时间】:2012-09-03 16:37:54
【问题描述】:

(来源于最近完成的一次编程竞赛)

在 1..10^7 范围内给你两个 10^5 整数数组:

int N[100000] = { ... }
int D[100000] = { ... }

想象有理数 X 是 N 的所有元素相乘并除以 D 的所有元素的结果。

修改两个数组而不改变 X 的值(并且不分配任何超出范围的元素),使得 N 的乘积和 D 的乘积没有公因数。

一个天真的解决方案(我认为)会起作用......

for (int i = 0; i < 100000; i++)
    for (int j = 0; j < 100000; j++)
    {
        int k = gcd(N[i], D[j]); // euclids algorithm

        N[i] /= k;
        D[j] /= k;
    }

...但这太慢了。

什么是少于大约 10^9 次操作的解决方案?

【问题讨论】:

  • 不知道您为什么发布带有答案链接的问题。
  • @RaymondChen:我发问题的时候没有解法代码,拿到解法代码的时候也看不懂,所以单独发一个问题解释一下,把它们联系起来。
  • 它出现在哪个比赛中?
  • 不记得了,CodeForces 还是 CodeChef。

标签: c algorithm math factorization


【解决方案1】:

让 O(sqrt(10^7) * 10^5) 中 N 和 D 中的每个元素因式分解为

N[i]=p1^kn1 * p2^kn2 ... 
D[i]=p1^kd1 * p2^kd2 ...

维护 2 个电源阵列,其中

Power_N[p1]=sum of kn1 for all i's
Power_D[p1]=sum of kd1 for all i's

Divide the N and D by p1^(min(Power_N[p1],Power_D[p2])) in O(10^5) each

【讨论】:

  • O 符号在这里感觉很奇怪。是为了补偿从 3162 到 1000 的舍入吗?
  • 是的。更准确地说,它应该是 O(sqrt(10^7)*10^5) 正如你所指出的那样。
  • 只要问题中没有O 符号,您就无法在答案中有意义地负担它。此外,整个输入是恒定大小的,因此整个计算必然是恒定时间,换句话说,O(1) 操作,甚至没有过多考虑要解决的任务,或者确切的输入大小;只要它是恒定的。
【解决方案2】:

因式分解 1 到 107 范围内的所有数字。 使用埃拉托色尼筛法的修改,您可以使用O(n*log log n) 空间在O(n*log n) 时间(我认为这更好一点,O(n*(log log n)²) 左右)内分解从 1 到 n 的所有数字。 del> 比这更好的可能是创建一个仅包含最小素因数的数组。

// Not very optimised, one could easily leave out the even numbers, or also the multiples of 3
// to reduce space usage and computation time
int *spf_sieve = malloc((limit+1)*sizeof *spf_sieve);
int root = (int)sqrt(limit);
for(i = 1; i <= limit; ++i) {
    spf_sieve[i] = i;
}
for(i = 4; i <= limit; i += 2) {
    spf_sieve[i] = 2;
}
for(i = 3; i <= root; i += 2) {
    if(spf_sieve[i] == i) {
        for(j = i*i, step = 2*i; j <= limit; j += step) {
            if (spf_sieve[j] == j) {
                spf_sieve[j] = i;
            }
        }
    }
}

要使用该筛子分解一个数字n &gt; 1,查找它的最小素因数p,确定它在分解n 中的多重性(通过递归查找,或者简单地除以直到p 没有'不要平均分配剩余的辅因子,这取决于)和辅因子。当辅因子大于 1 时,查找下一个主因子并重复。

创建一个从素数到整数的映射

遍历两个数组,对于N 中的每个数字,将其分解中每个素数的指数添加到映射中的值,对于D 中的数字,减去。

遍历地图,如果素数的指数是正数,则在数组N 中输入p^exponent(如果指数太大,您可能需要将其拆分为多个索引,对于较小的值,请合并几个素数进入一个条目 - 有 664579 个素数小于 107,因此数组中的 100,000 个插槽可能不足以存储每个出现的素数具有正确的幂),如果指数为负,对 D 数组执行相同操作,如果为 0,则忽略该素数。

然后将ND 中的所有未使用插槽设置为1。

【讨论】:

  • 我知道如何使用 Sieve 来查找素数,但是您如何使用它来查找素数分解?你有网络参考吗?
  • 您不仅要标记一个数是否是合数,还要记录素数除数。实际上,我刚刚想到,只记录每个数字的最小素因子可能会更好 - 也更容易,然后您可以使用它来递归地分解两个数组中的数字。网络参考,我手头没有,除了也许我可以链接到a Haskell implementation 这样一个最小的素因子筛。
  • 寻找素数分解(或绕过该操作)是问题的真正挑战所在。我不认为挥手是件好事。
  • 我最近在programmingpraxis.com/2012/05/08/factor-tables讨论了筛选每个数字范围中的最小质因数。
  • @DanielFischer:见这里:stackoverflow.com/questions/12359785/…
【解决方案3】:

分解任一数组的每个元素,排序,取消。对于有界大小的整数,因式分解是常数时间,排序是 n log n,取消将是线性的。不过,常数因子可能很大。

如果您尝试降低实际执行时间而不是降低渐近复杂度,那么通过手动取消小因素(例如 2、3、5 和 7 的幂)来预处理数组可能不会有什么坏处。概率(即除了病态输入),这将极大地加速大多数算法,代价是一些线性时间传递。

整合上述方法的一种更复杂的方法是首先构建一个素数列表,直到sqrt(10^7) ~= 3162。根据素数定理,应该有大约3162/ln(3162) ~= 392 这样的素数。 (事实上​​,为了节省运行时间,您可以/应该预先计算此表。)

然后,对于N 中的每个此类整数,以及每个素数,将整数减去该素数,直到它不再均匀除,并且每次增加该素数的计数。对D 执行相同的操作,而是递减。一旦你浏览了素数表,当且仅当它是大于 3162 的素数时,当前 int 将是非 1。这应该是每个数组中总整数的 7% 左右。你可以把它们放在一堆或类似的地方。将它们也设置为数组中的值。

最后,您迭代正因子并将它们的乘积放入 N。您可能需要将其拆分到多个数组槽中,这很好。将负面因素放入D,就大功告成了!

这需要我一分钟的运行时间来解决。希望是合理的。

【讨论】:

    【解决方案4】:

    几乎所有的东西都写好了,我建议 设 p=(N 中所有元素的乘积)
    让 q=(D 中所有元素的乘积)
    X=(p/q);应该始终保持不变
    求 p,q 的素因数;
    通过可能将他们的幂存储在矩阵 a[0](2 的幂)、a[1](3 的幂)、a[2](5 的幂)等中。 现在您可以比较矩阵中的值并将较低的一的幂降低到零。
    例如。 p=1280 q=720
    对于 p a[0]=8(2 的幂) a[1]=0(3 的幂) a[2]=1(5 的幂);
    对于 q b[0]=4 b[1]=2 b[2]=1;

    为索引 0,1,2 设置一个/两个(如果两者相等)值/s 零......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多