按顺序计算互质

时间:2014-07-17 15:02:22

标签: algorithm primes counting

具有n <= 10 ^ 6个整数的序列,全部不超过m <= 3 * 10 ^ 6,我想计算其中有多少个互质对。如果最大公约数为1,则两个数字是互质的。

它可以在O(n ^ 2 log n)中平凡地完成,但这显然是缓慢的方式,因为极限表明更接近O(n log n)。可以快速完成的一件事就是将所有数字分解出去,并且每次都丢掉同一个素数的多次出现,但这并没有带来任何显着的改善。我还想过计算相反的 - 具有公约数的对。它可以分组完成 - 首先计算它们最小公共素数除数为2,然后是3,5等的所有对,但在我看来它似乎是另一个死胡同。

5 个答案:

答案 0 :(得分:6)

我根据你的回答提出了一个稍微快一点的选择。在我的工作PC上我的C ++实现(底部)需要 350ms 来解决任何问题实例;在我的旧笔记本电脑上,它只需要超过1秒。该算法避免了所有除法和模运算,并且仅使用O(m)空间。

与您的算法一样,基本思想是通过枚举每个数字2&lt; = i&lt; = m来应用包含 - 排除原则,其中不包含任何重复因子一次,并且对于每个这样的i,计算数量输入中的数字可以被i整除,并且可以从总数中加上或减去它。关键的区别在于我们可以简单地通过测试输入中是否出现i的每个可能倍数来实现计数部分&#34; ,这仍然只需要O(m log m)时间。

c += v[j].freq;中最里面的行countCoprimes()重复多少次?对于每个不包含重复素数因子的数字2 <= i <= m,执行外环的主体一次;这个迭代计数通常由m上限。内循环在i [2..m]范围内一次一步地前进,因此它在单个外循环迭代期间执行的操作数量由m / i上限。因此,最内线的迭代总数上限为从i = 2到m的m / i之和。 m因子可以移到总和之外以获得

的上限
m * sum{i=2..m}(1/i)

该和是谐波系列中的部分和,it is upper-bounded by log(m),因此最内层循环迭代的总数为O(m log m)。

extendedEratosthenes()旨在通过避免所有划分并保持O(m)内存使用来减少常数因子。所有countCoprimes()实际上需要知道数字2&lt; = i&lt; = m是(a)它是否有重复的素数因子,如果它没有,(b)它是否具有偶数或奇数个素因子。为了计算(b)我们可以有效地利用Eratosthenes的筛子&#34;击中&#34;任何给定的i,其不同的素因子按递增顺序排列,因此我们可以稍微翻转一下(parity中的struct entry字段)以跟踪我是否具有偶数或奇数个因子。每个数字都以prod字段等于1开头;记录(a)我们只是&#34;淘汰&#34;通过将其prod字段设置为0,将包含素数的平方作为因子的任何数字。此字段用于双重目的:如果v[i].prod == 0,则表示发现我有重复因子;否则它包含到目前为止发现的(必然是不同的)因素的产物。这个(相当小的)效用是它允许我们停止m的平方根处的主筛环,而不是一直到m:现在,对于没有重复因子的任何给定i, v[i].prod == i,在这种情况下,我们找到了i或v[i].prod < i的所有因素,在这种情况下,我必须正好一个因子&gt; sqrt(3000000),我们还没有考虑到。我们可以找到所有这些剩余的&#34;大因素&#34;使用第二个非嵌套循环。

#include <iostream>
#include <vector>

using namespace std;

struct entry {
    int freq;       // Frequency that this number occurs in the input list
    int parity;     // 0 for even number of factors, 1 for odd number
    int prod;       // Product of distinct prime factors
};

const int m = 3000000;      // Maximum input value
int n = 0;                  // Will be number of input values
vector<entry> v;

void extendedEratosthenes() {
    int i;
    for (i = 2; i * i <= m; ++i) {
        if (v[i].prod == 1) {
            for (int j = i, k = i; j <= m; j += i) {
                if (--k) {
                    v[j].parity ^= 1;
                    v[j].prod *= i;
                } else {
                    // j has a repeated factor of i: knock it out.
                    v[j].prod = 0;
                    k = i;
                }
            }
        }
    }

    // Fix up numbers with a prime factor above their square root.
    for (; i <= m; ++i) {
        if (v[i].prod && v[i].prod != i) {
            v[i].parity ^= 1;
        }
    }
}

void readInput() {
    int i;
    while (cin >> i) {
        ++v[i].freq;
        ++n;
    }
}

void countCoprimes() {
    __int64 total = static_cast<__int64>(n) * (n - 1) / 2;
    for (int i = 2; i <= m; ++i) {
        if (v[i].prod) {
            // i must have no repeated factors.

            int c = 0;
            for (int j = i; j <= m; j += i) {
                c += v[j].freq;
            }

            total -= (v[i].parity * 2 - 1) * static_cast<__int64>(c) * (c - 1) / 2;
        }
    }

    cerr << "Total number of coprime pairs: " << total << "\n";
}

int main(int argc, char **argv) {
    cerr << "Initialising array...\n";
    entry initialElem = { 0, 0, 1 };
    v.assign(m + 1, initialElem);

    cerr << "Performing extended Sieve of Eratosthenes...\n";
    extendedEratosthenes();

    cerr << "Reading input...\n";
    readInput();

    cerr << "Counting coprimes...\n";
    countCoprimes();

    return 0;
}

答案 1 :(得分:0)

进一步利用我在问题中提到的想法,我实际上自己想出了一个解决方案。由于你们中的一些人可能对此感兴趣,我将简要介绍一下。它在O(m log m + n)中工作,我已经用C ++实现并测试 - 在不到5秒的时间内解决了最大的情况(10 ^ 6整数)。

我们有n个整数,都不大于m。我们首先做Eratosthenes Sieve将每个整数映射到m的最小素数因子,允许我们在O(log m)时间内计算出任何不大于m的数。那么对于所有给定的数A [i],只要有一个素数p而不是A [i]在一个大于1的幂中,我们将A [i]除以它,因为当询问两个数字是否是互质时我们可以省略指数。这让我们所有A [i]都是不同素数的产品。

现在,让我们假设我们能够在合理的时间内构造表T,使得T [i]是条目数A [j],使得i除以A [j]。这在某种程度上类似于@Brainless在他的第二个答案中采用的方法。快速构建表格T是我在我的问题下面的评论中谈到的技术。

从现在起,我们将按照包含 - 排除原则开展工作。具有T,对于每个i,我们计算P [i] - 对(j,k)的量,使得A [j]和A [k]都可以被i整除。然后计算答案,求和所有P [i],在那些具有偶数个除数的P [i]之前取减号。注意i的所有素数除数都是不同的,因为对于所有其他指数,i P [i]等于0.通过包含 - 排除每对将仅计数一次。为了看到这一点,采用A [i]和A [j]对,假设它们共享k个共同的除数。然后这对将被计数k次,然后打折kC2次,计数kC3次,打折kC4次......对于nCk,请参见牛顿符号。一些数学操作使我们看到所考虑的对将被计数1 - (1-1)^ k = 1次,证据的结论是什么。

到目前为止所做的步骤需要筛选O(m log log m)和计算结果的O(m)。最后要做的是构造数组T.我们可以为每个A [i]增加所有j除以i的T [j]。由于A [i]最多可以有O(sqrt(A [i])个除数(实际上甚至小于该除数),那么我们可以在O(n sqrt m)中构造T.但我们可以做得更好!

取二维数组W.每个时刻都有一个跟随不变量 - 如果对于每个非零W [i] [j],我们会将表格T中的计数器增加W [i] [j]所有数字除了i,并且还分享了我在i的j个最小素数除数中的精确指数,那么T将被正确构造。由于这看起来有点令人困惑,让我们看看它在行动。在开始时,为了使不变量为真,对于每个A [i]我们只增加W [A [i]] [0]。另请注意,不超过m的数字最多可以有O(log m)个除数,因此W的总大小为O(m log m)。现在我们看到存储在W [i] [j]中的信息可以被推送到#34;通过以下方式:将p视为i的(j + 1) - 素数除数,假设它有一个。那么我的一些除数可以是p,其指数与i相同或更低。这些案例中的第一个是W [i] [j + 1] - 我们添加另一个必须完全采取的素数&#34;通过除数。第二种情况是W [i / p] [j]作为i的除数,它没有具有最高指数的p也必须除以i / p。就是这样!我们按降序排列所有i,然后按升序排列j。我们&#34;推进&#34;来自W [i] [j]的信息。看看如果我有完全j的除数,那么它的信息就无法推送,但我们并不真的需要它!如果我有j个除数,那么W [i] [j]基本上说:在数组T中只增加W [i] [j] 索引i所以当所有信息都被推到&#34;最后一行&#34;在每个W [i]中,我们通过这些行并完成构造T.当W [i] [j]的每个单元被访问过一次时,该算法需要O(m log m)时间,并且O(n)at开始时。结束了这个结构。这是来自实际实现的一些C ++代码:

FORD(i,SIZE(W)-1,2) //i in descending order
{
    int v = i, p;

    FOR(j,0,SIZE(W[i])-2) //exclude last row
    {
        p = S[v]; //j-th divisor; S[v] - smallest prime divisor of v
        while (v%p == 0) v /= p;

        W[i][j+1] += W[i][j];
        W[i/p][j] += W[i][j];
    }

    T[i] = W[i].back();
}

最后,我说我认为阵列T的构建速度比我所展示的更快更简单。如果有人对如何做到这一点有一些巧妙的想法,我将非常感谢所有的反馈。

答案 2 :(得分:0)

以下是基于http://oeis.org/A018805上找到的完整序列1..n的公式的想法:

a(n) = 2*( Sum phi(j), j=1..n ) - 1, where phi is Euler's totient function

迭代序列S。对于每个字词S_i

对于p的每个素数因子S_i
如果p的哈希值不存在:
创建索引为p的哈希,该哈希指向除S之外的i所有索引的集合,
并且计数器设置为1,表示到目前为止S可以将p的多少项除尽 其他:
删除现有索引集中的i并递增计数器

按其计数器降序排列S_i的素因子的哈希值。以
开头 最大的计数器(表示最小的集合),列出最多i的索引列表 下一个最小集合的成员,直到集合耗尽。添加剩余的数量
列表中的索引为累计总数。

示例:

sum phi' [4,7,10,15,21]

S_0: 4
prime-hash [2:1-4], counters [2:1]
0 indexes up to i in the set for prime 2
total 0

S_1: 7
prime hash [2:1-4; 7:0,2-4], counters [2:1, 7:1]
1 index up to i in the set for prime 7
total 1

S_2: 10
prime hash [2:1,3-4; 5:0-1,3-4; 7:0,2-4], counters [2:2, 5:1, 7:1]
1 index up to i in the set for prime 2, which is also a member 
of the set for prime 5
total 2

S_3: 15
prime hash [2:1,3-4; 5:0-1,4; 7:0,2-4; 3:0-2,4], counters [2:2: 5:2, 7:1, 3:1]
2 indexes up to i in the set for prime 5, which are also members 
of the set for prime 3
total 4

S_4: 21
prime hash [2:1,3-4; 5:0-1,4; 7:0,2-3; 3:0-2], counters [2:2: 5:2, 7:2, 3:2]
2 indexes up to i in the set for prime 7, which are also members 
of the set for prime 3
total 6

6 coprime pairs:
(4,7),(4,15),(4,21),(7,10),(7,15),(10,21)

答案 3 :(得分:-1)

我建议:

1)使用Eratosthene获得10 ^ 6下的分类素数列表。

2)对于列表中的每个数字n,得到它的主要因素。通过以下方式将其与另一个数字f(n)相关联:假设n的素因子是3,7和17.那么f(n)的二进制表示是:

`0 1 0 1 0 0 1`

第一个数字(此处为0)与素数2相关联,第二个数字(此处为1)与素数3相关联等...

因此 2个数字n和m是互质的iff f(n)&amp; f(m)= 0

3)很容易看出有一个N对于每个n:f(n)&lt; =(2 ^ N) - 1.这意味着最大数f(n)小于或等于一个二进制表示为的数字:

`1 1 1 1 1 1 1 1 1 1 1 1 1 1 1`

这里N是上述序列中的1的数。得到这个N并对数字列表f(n)进行排序。我们叫这个清单L. 如果要优化:在此列表中,不是对重复项进行排序,而是存储包含f(n)和f(n)重复次数的对。

4)以这种方式从1到N迭代:初始化i = 1 0 0 0 0,并且在每次迭代时,将数字1向右移动,其他所有值保持为0(使用bitshift实现它)。 / p>

在每次迭代时,迭代L以得到L中元素l的数量d(i),使得i&amp; l!= 0(使用上述优化时要小心)。换句话说,对于每个i,得到L中与i不相互作用的元素数,并将该数字命名为d(i)。添加总数

D = d(1) + d(2) + ... + d(N)

5)此数字D是原始列表中不互质的对数。互质对的数量是:

M*(M-1)/2 - D

其中M是原始列表中的元素数。这种方法的复杂性是O(n log(n))。

祝你好运!

答案 4 :(得分:-1)

我之前的回答是错误的,道歉。我在这里提出一个修改:

一旦得到列表中每个数字的主要除数,就将每个素数p与列表中以p作为除数的数字l(p)相关联。例如,考虑素数5,可以除以5的列表数是15,100和255.然后l(5)= 3.

要在O(n logn)中实现它,迭代列表并对该列表中的每个数字迭代它的素数因子;对于每个素因子p,增加其l(p)。

然后,不是互质的并且可以除以p的对的数量是

l(p)*(l(p) - 1) / 2

对所有素数p求和,你将得到列表中不是互质的对数(注意l(p)可以是0)。假设这个总和是D,那么答案就是

M*(M-1)/2 - D

其中M是列表的长度。祝你好运!