求除数对

Finding divisor pairs

本文关键字:      更新时间:2023-10-16

我正在尝试解决这个练习http://main.edu.pl/en/archive/amppz/2014/dzi,我不知道如何提高我的代码的性能。当程序必须处理超过500,000个唯一数字(如描述中最多2,000,000个)时,就会出现问题。然后用1-8秒循环遍历所有这些数字。我使用的测试来自http://main.edu.pl/en/user.phtml?op=tests&c=52014&task=1263,我通过命令
进行测试program.exe < data.in > result.out

描述:You are given a sequence of n integer a1, a2, ... an. You should determine the number of such ordered pairs(i, j), that i, j equeals(1, ..., n), i != j and ai is divisor of aj.
The first line of input contains one integer n(1 <= n <= 2000000) The second line contains a sequence of n integers a1, a2, ..., an(1 <= ai <= 2000000).
In the first and only line of output should contain one integer, denoting the number of pairs sought.
For the input data: 5 2 4 5 2 6
the correct answer is: 6
Explanation: There are 6 pars: (1, 2) = 4/2, (1, 4) = 2/2, (1, 5) = 6/2, (4, 1) = 2/2, (4, 2) = 4/2, (4, 5) = 6/2.

例如

:
-总数为2M,唯一编号为635k,总共有3.45亿次迭代
-总共有2M个数字和200万个唯一数字,总共有1.885亿次迭代

#include <iostream>
#include <math.h>
#include <algorithm>
#include <time.h>

#define COUNT_SAME(count) (count - 1) * count

int main(int argc, char **argv) {
    std::ios_base::sync_with_stdio(0);
    int n; // Total numbers
    scanf("%d", &n);
    clock_t start, finish;
    double  duration;
    int minVal = 2000000;
    long long *countVect = new long long[2000001]; // 1-2,000,000; Here I'm counting duplicates
    unsigned long long counter = 0;
    unsigned long long operations = 0;
    int tmp;
    int duplicates = 0;
    for (int i = 0; i < n; i++) {
        scanf("%d", &tmp);
        if (countVect[tmp] > 0) { // Not best way, but works
            ++countVect[tmp];
            ++duplicates;
        } else {
            if (minVal > tmp)
                minVal = tmp;
            countVect[tmp] = 1;
        }
    }
    start = clock();
    int valueJ;
    int sqrtValue, valueIJ;
    int j;
    for (int i = 2000000; i > 0; --i) {
        if (countVect[i] > 0) { // Not all fields are setted up
            if (countVect[i] > 1) 
                counter += COUNT_SAME(countVect[i]); // Sum same values
            sqrtValue = sqrt(i);
            for (j = minVal; j <= sqrtValue; ++j) {
                if (i % j == 0) {
                    valueIJ = i / j;
                    if (valueIJ != i && countVect[valueIJ] > 0 && valueIJ > sqrtValue)
                        counter += countVect[i] * countVect[valueIJ];
                    if (i != j && countVect[j] > 0)
                        counter += countVect[i] * countVect[j];
                }
                ++operations;
            }
        }
    }
    finish = clock();
    duration = (double)(finish - start) / CLOCKS_PER_SEC;
    printf("Loops time: %2.3f", duration);
    std::cout << "sn";
    std::cout << "nnCounter: " << counter << "n";
    std::cout << "Total operations: " << operations;
    std::cout << "nDuplicates: " << duplicates << "/" << n;
    return 0;
}  

我知道,我不应该在一开始就对数组进行排序,但是我不知道如何使它更好。

任何提示将是伟大的,谢谢!

这里是改进的算法- 0.5s内2M唯一数。感谢@ pjtrail !

#include <iostream>
#include <math.h>
#include <algorithm>
#include <time.h>

#define COUNT_SAME(count) (count - 1) * count

int main(int argc, char **argv) {
    std::ios_base::sync_with_stdio(0);
    int n; // Total numbers
    scanf("%d", &n);
    clock_t start, finish;
    double  duration;
    int maxVal = 0;
    long long *countVect = new long long[2000001]; // 1-2,000,000; Here I'm counting duplicates
    unsigned long long counter = 0;
    unsigned long long operations = 0;
    int tmp;
    int duplicates = 0;
    for (int i = 0; i < n; i++) {
        scanf("%d", &tmp);
        if (countVect[tmp] > 0) { // Not best way, but works
            ++countVect[tmp];
            ++duplicates;
        } else {
            if (maxVal < tmp)
                maxVal = tmp;
            countVect[tmp] = 1;
        }
    }
    start = clock();
    int j;
    int jCounter = 1;
    for (int i = 0; i <= maxVal; ++i) {
        if (countVect[i] > 0) { // Not all fields are setted up
            if (countVect[i] > 1)
                counter += COUNT_SAME(countVect[i]); // Sum same values
            j = i * ++jCounter;
            while (j <= maxVal) {
                if (countVect[j] > 0)
                    counter += countVect[i] * countVect[j];
                j = i * ++jCounter;
                ++operations;
            }
            jCounter = 1;
        }
    }
    finish = clock();
    duration = (double)(finish - start) / CLOCKS_PER_SEC;
    printf("Loops time: %2.3f", duration);
    std::cout << "sn";
    std::cout << "nnCounter: " << counter << "n";
    std::cout << "Total operations: " << operations;
    std::cout << "nDuplicates: " << duplicates << "/" << n;
    return 0;
}

我希望下面的工作比OP的算法快得多(优化斜):

  • (值和频率的类型应为32位无符号,计数为64位-在计算计数之前进行提升,如果您的语言不这样做。)
  • 读取值的个数,n
  • 读取每个值v,在其频率[v]上加1(不需要存储)。
    • (freq[MAX](或MAX+1)可以静态分配,可能是最优初始化到所有0)
  • 计算freq[1]中包含1的对的个数和值的个数
  • 对于每1在2..MAX (with freq[i]> 0):
    • 从freq[i]计算(i,i)对的个数
    • 对于i在2m..MAX中的每一个倍数m:
      • (使用m作为循环计数器并增加它,而不是乘以它)
      • 从freq[i]和freq[m]计算对(i,m)的个数
    • (如果freq[i] = 1,可以省略(i,i)计算并执行针对freq[i] = 1优化的循环的变体)
  • (可以执行前面的(外部)循环从2..MAX/2,然后从MAX/2+1…MAX省略处理多个)

对的数量(我)= <子>频率[我] C <子> 2子> i,j) = freq[i] * freq[j]对于i≠j

这避免了排序、sqrt和除法。

其他优化

可以存储不同的值,而不是扫描数组(顺序无关);由此产生的增益或损失取决于1..MAX中值的密度。

如果最大频率为<216,这听起来很有可能,所有的产品都适合32位。可以利用这一点,将数值类型作为模板编写函数,跟踪最大频率,然后为其余部分选择适当的模板实例。这需要N*(比较+分支),并且可以通过使用32位而不是64位执行D2乘法来获得收益,其中D是不同值的数量。除了N &lt之外,我认为没有简单的方法可以推断出32位足以满足总数。2 <一口> 16> p>如果对n处理器并行化,可以让不同的处理器处理以n模的不同残数。

我考虑过跟踪偶数值的数量,以避免扫描一半的频率,但我认为对于给定参数内的大多数数据集,这将产生很少的优势。

好吧,我不打算把整个算法都写出来,但它肯定可以更快。所以我猜这就是你需要做的:

你的列表已经排序了,所以你可以从这里做出很多假设。以最大值为例。它不会有任何倍数。值的最大值,将最大值除以2。

这里还有一个非常有用的事实。一个倍数的倍数也是一个倍数。(仍然在吗?))。以列表[2 4 12]为例。现在你发现(4,12)是多对的。如果你现在也找到了(2,4),那么你可以推断出12也是2的倍数。因为你只需要数成对的数,你只需要数一下每个数有多少倍数,当你看到这个数本身是倍数时,把它加起来。这意味着最好是向后迭代排序列表,而不是寻找除数。

可能以这样的方式存储它[ (three 2's ), (two 5's), ...]ie。存储一个数字出现的频率。同样,您不需要跟踪它的id,因为您只需要为它们提供配对的总数。这样存储你的表很有帮助,因为所有的2都有相同数量的倍数。计算一次,然后相乘。

相关文章:
  • 没有找到相关文章