【问题标题】:Find the sum of least common multiples of all subsets of a given set求给定集合的所有子集的最小公倍数之和
【发布时间】:2014-12-15 07:03:03
【问题描述】:

给定: 设置A = {a<sub>0</sub>, a<sub>1</sub>, ..., a<sub>N-1</sub>} (1 &amp;leq; N &amp;leq; 100) 和2 &amp;leq; a<sub>i</sub> &amp;leq; 500

问:A 大小至少为 2 的所有子集的所有最小公倍数 (LCM) 之和。

集合B = {b<sub>0</sub>, b<sub>1</sub>, ..., b<sub>k-1</sub>} 的 LCM 定义为最小整数 B<sub>min</sub> 使得 b<sub>i</sub> | B<sub>min</sub>,对于所有 0 &amp;leq; i &lt; k

示例:

N = 3A = {2, 6, 7},那么:

LCM({2, 6})      =    6
LCM({2, 7})      =   14
LCM({6, 7})      =   42
LCM({2, 6, 7})   =   42
----------------------- +
answer              104

天真的方法是简单地计算所有 O(2<sup>N</sup>) 子集的 LCM,这对于相当大的 N 是不可行的。


解决方案草图:

问题来自一个竞赛*,该竞赛还提供了solution sketch。这就是我的问题所在:我不明白暗示的方法。

解决方案如下(以一些小的固定语法问题为模):

解决方案有点棘手。如果我们仔细观察,我们会发现整数介于2500 之间。因此,如果我们对这些数字进行质因数分解,我们会得到以下最大幂:

 2 8  
 3 5
 5 3
 7 3
11 2
13 2
17 2
19 2

除此之外,所有素数的幂都是 1。因此,我们可以使用这些整数轻松计算所有可能的状态,留下 9 * 6 * 4 * 4 * 3 * 3 * 3 * 3 状态,这几乎是 70000。对于其他整数,我们可以制作如下所示的 dp:dp[70000][i],其中i 可以是0100。但是,由于dp[i] 依赖于dp[i-1],所以dp[70000][2] 就足够了。这将复杂性留给n * 70000,这是可行的。

我有以下具体问题:

  • 这些状态是什么意思?
  • dp 是否代表动态规划?如果是,解决的是什么递归关系?
  • dp[i] 是如何从dp[i-1] 计算出来的?
  • 为什么大素数对状态数没有贡献?它们中的每一个都出现01 次。对于这些素数中的每一个,状态数是否不应该乘以2(再次导致不可行的状态空间)?

*原问题描述可参考this source(问题F)。这个问题是该描述的简化版本。

【问题讨论】:

  • @PhamTrung "state" 是什么意思,dp[state][i] 是什么意思,以及如何翻译 dp[f(state)][i] = g( dp[state][i -1])
  • @MasterMind 我对这个问题感到沮丧,因为自从我读到你的问题后,它就一直萦绕在我的脑海中:) 我试图重新表述这个问题,以便(希望)吸引更多答案。我想我尽可能接近您最初问题的核心,但请您验证一下吗?
  • @VincentvanderWeele 非常感谢,你真的完全理解我。您所做的修改令人惊叹,您提出的问题是核心和问题的核心。再来一次,谢谢
  • Stack Overflow 不是一个要求人们做功课的网站...

标签: algorithm computer-science dynamic-programming primes prime-factoring


【解决方案1】:

讨论

在阅读了实际的比赛描述 (page 10 or 11) 和解决方案草图后,我不得不得出结论,解决方案草图的作者的写作相当不准确。

如果组件是通过公平抛硬币随机选择的,那么高级问题是计算预期寿命。这就是导致计算所有子集的 LCM 的原因——所有子集都有效地代表了样本空间。您最终可能会得到任何可能的组件集。设备的故障时间基于设备的 LCM。因此,预期寿命是所有集合的 LCM 的平均值。

请注意,这应该包括只有一个项目的集合的 LCM(在这种情况下,我们假设 LCM 是元素本身)。解决方案草图似乎在破坏,可能是因为他们以不那么优雅的方式处理它。

这些状态是什么意思?

草图作者只使用了两次state这个词,但显然设法转换了含义。在第一次使用 state 一词时,他们似乎在谈论可能的组件选择。在第二次使用中,他们可能会谈论可能的故障时间。他们可能混淆了这个术语,因为他们的动态编程解决方案会根据单词的一种用法初始化值,而递归关系源于另一种。

dp 代表动态规划吗?

我会说要么确实如此,要么只是巧合,因为解决方案草图似乎在很大程度上暗示了动态规划。

如果是这样,正在解决什么递归关系? dp[i] 是如何从 dp[i-1] 计算出来的?

我能想到的是,在他们的解决方案中,state i 表示失败时间,T(i),计算了这次失败的次数,dp[i] .得到的总和是所有 dp[i] * T(i) 的总和。

dp[i][0] 将是仅计算第一个组件的故障时间。 dp[i][1] 将是计算第一个和第二个组件的故障时间。 dp[i][2] 将用于第一个、第二个和第三个。等等。

用零初始化dp[i][0],除了dp[T(c)][0](其中c 是第一个考虑的组件)应该是1(因为到目前为止该组件的故障时间已经计算过一次)。

dp[i][n-1] 为每个组件c 填充dp[i][n]

  • 对于每个i,将dp[i][n-1] 复制到dp[i][n]
  • dp[T(c)][n]加1。
  • 对于每个i,将dp[i][n-1] 添加到dp[LCM(T(i), T(c))][n]

这是在做什么?假设您知道您的故障时间为j,但您添加了一个故障时间为k 的组件。不管你以前有什么组件,你的新失败时间是LCM(j, k)。这是因为对于两个集合ABLCM(A union B} = LCM(LCM(A), LCM(B))

类似地,如果我们正在考虑T(i) 的故障时间和T(c) 的新组件的故障时间,则最终的故障时间为LCM(T(i), T(c))。请注意,我们记录了 dp[i][n-1] 配置的失败时间,因此一旦引入新组件,我们应该记录许多新的失败时间。

为什么大素数对状态数没有贡献?

它们中的每一个都出现 0 次或 1 次。对于这些素数中的每一个,状态数是否不应该乘以 2(再次导致不可行的状态空间)?

你是对的,当然。但是,解决方案草图指出具有大素数的数字以另一种(未指定)方式处理。

如果我们确实包含它们会发生什么?我们需要表示的状态的数量会爆炸成一个不切实际的数字。因此,作者对这些数字的解释不同。请注意,如果小于或等于 500 的数字包括大于 19 的素数,则其他因子乘以 21 或更少。这使得这些数字可以进行暴力破解,不需要表格。

【讨论】:

  • 我认为这完全回答了具体问题。我发现关于递归关系的部分有点难以理解(我不确定你添加的上下文是否比你刚刚解决抽象问题时更简单),但它似乎是正确的。
  • 诚然,我非常努力地与草图作者的措辞保持一致——听起来作者在他们的脑海中有某种模式,他们使用的术语来自,即使它不匹配手头的问题。所以我认为你可能是对的——我最终得到了两全其美的结果——有些不正确的术语和解决问题的深奥方法。感谢您揭露这一点——它将帮助我改进未来的答案。
【解决方案2】:

社论的第一部分似乎很有用,但第二部分相当模糊(也许没有帮助;我宁愿完成这个答案也不愿弄清楚)。

假设输入由成对不同的素数组成,例如 2、3、5 和 7。那么答案(对所有集合求和,其中 0 个整数的 LCM 为 1)是

(1 + 2) (1 + 3) (1 + 5) (1 + 7),

因为一个子集的 LCM 正好等于这里的乘积,所以直接相乘即可。

让我们放宽素数成对不同的限制。如果我们有一个像 2、2、3、3、3 和 5 这样的输入,那么乘法看起来像

(1 + (2^2 - 1) 2) (1 + (2^3 - 1) 3) (1 + (2^1 - 1) 5),

因为 2 以重数 2 出现,3 以重数 3 出现,而​​ 5 以重数 1 出现。例如,对于仅 3 的集合,有2^3 - 1 方法可以选择包含 3 的子集, 和1 选择空集的方式。

如果小于或等于 19,则调用素数 small,否则调用 large。请注意,小于等于 500 的整数最多可被一个大素数整除(具有多重性)。小素数问题更大。我们要做的是,对于 LCM 素数分解的每个可能的小部分(即约 70,000 个状态之一),计算通过丢弃不能除的整数得出的问题的 LCM 总和这样的 LCM,只为其他整数留下较大的素数因子(或 1)。

比如输入为2、30、41、46、51,状态为2,那么我们将2保留为1,丢弃30(= 2 * 3 * 5;3和5很小) ,将 41 保留为 41(41 大),将 46 保留为 23(= 2 * 23;23 大),丢弃 51(= 3 * 17;3 和 17 小)。现在,我们使用前面描述的技术计算 LCM 的总和。使用inclusion-exclusion 去除其 LCM 的子集,其小部分正确划分状态而不是完全相等。也许我稍后会写一个完整的例子。

【讨论】:

  • 仅供参考:看来,OP 专门询问了第二部分...如果您不理解某些内容,它并不自动意味着它无益
  • @arturgrzesiak 不太礼貌,我认为这是错误的,但它是如此模糊以至于可以说它甚至没有错。
  • 如果您更具体地添加如何在这种情况下使用包含-排除,它仍然会有所帮助,但这个答案提供了许多有用的指针。
【解决方案3】:

What is meant by these states?

我认为这里的状态是指数字是否在集合 A 的 LCM 的集合 B = {b0, b1, ..., bk-1} 中。

Does dp stand for dynamic programming and if so, what recurrence relation is being solved?

我相信,解决方案草图中的 dp 代表动态编程。

How is dp[i] computed from dp[i-1]?

我们可以从之前的状态中找出下一组 LCM 的状态是可行的。所以,我们只需要 2 的数组,然后来回切换。

Why do the big primes not contribute to the number of states? Each of them occurs either 0 or 1 times. Should the number of states not be multiplied by 2 for each of these primes (leading to a non-feasible state space again)?

我们只能使用素数分解和指数来表示数字。

这是一个例子。

6 = (2^1)(3^1)(5^0) -> 状态“1 1 0”表示 6 18 = (2^1)(3^2)(5^0) -> 状态“1 2 0”代表18

这是我们如何使用素数分解获得 6 和 18 的 LMC

LCM (6,18) = (2^(max(1,1)) (3^ (max(1,2)) (5^max(0,0)) = (2^1)(3 ^2)(5^0) = 18

2^9 > 500, 3^6 > 500, 5^4 > 500, 7^4>500, 11^3 > 500, 13^3 > 500, 17^3 > 500, 19^3 > 500

我们只能使用素数 2,3,5,7,11,13,17,19 的指数来表示集合 B = {b0, b1, ..., bk-1} 中的 LCM 对于给定的集合 A = {a0, a1, ..., aN-1} (1 ≤ N ≤ 100),其中 2 ≤ ai ≤ 500。

9 * 6 * 4 * 4 * 3 * 3 * 3 * 3

我编写了一个小的 C++ 程序来说明我们如何获得给定集合 A = {a0, a1, ..., aN-1} (1 ≤ N ≤ 100) 的 LCM 的总和,其中 2 ≤ ai ≤ 500。在解决方案草图中,我们需要循环最大可能的 70000 个 LCM。

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

int lcm(int a, int b) {
    if (a == 0 || b == 0) {
        return 0;
    }
    return (a * b) / gcd(a, b);
}


int sum_of_lcm(int A[], int N) {

    // get the max LCM from the array
    int max = A[0];
    for (int i = 1; i < N; i++) {
        max = lcm(max, A[i]);
    }
    max++;

    //
    int dp[max][2];
    memset(dp, 0, sizeof(dp));
    int pri = 0;
    int cur = 1;

    // loop through n x 70000
    for (int i = 0; i < N; i++) {
        for (int v = 1; v < max; v++) {
            int x = A[i];
            if (dp[v][pri] > 0) {
                x = lcm(A[i], v);
                dp[v][cur] = (dp[v][cur] == 0) ? dp[v][pri] : dp[v][cur];
                if ( x % A[i] != 0 ) {
                    dp[x][cur] += dp[v][pri] + dp[A[i]][pri];
                } else {
                    dp[x][cur] += ( x==v ) ? ( dp[v][pri] + dp[v][pri] ) : ( dp[v][pri] ) ;
                }
            }
        }
        dp[A[i]][cur]++;
        pri = cur;
        cur = (pri + 1) % 2;
    }

    for (int i = 0; i < N; i++) {
        dp[A[i]][pri] -= 1;
    }
    long total = 0;
    for (int j = 0; j < max; j++) {
        if (dp[j][pri] > 0) {
            total += dp[j][pri] * j;
        }
    }
    cout << "total:" << total << endl;

    return total;
}



int test() {
    int a[] = {2, 6, 7 };
    int n = sizeof(a)/sizeof(a[0]);
    int total = sum_of_lcm(a, n);
    return 0;
}

Output
total:104

【讨论】:

  • 尝试使用输入 [2^8, 3^5, ..., 491, 499] 运行您的示例代码。数组A 的大小略高于 70000 然后...
  • 这是一个用于计算LCM总和的DP的插图,一个想法。
【解决方案4】:

状态比素数的幂多一种。您的数字最多为 2^8,因此 2 的幂在 [0..8] 中,即 9 个状态。其他州也一样。

“dp”可以代表动态编程,我不确定。

递归关系是问题的核心,因此您将通过自己解决来了解更多信息。从一些小而简单的例子开始。

对于大素数,尝试在不使用它们(或它们的等价物)的情况下解决一个简化的问题,然后将它们加回以查看它们对最终结果的影响。

【讨论】:

  • 我觉得这个答案解释了除了困难部分之外的一切。建议的解决方案的动态规划方面是什么?
  • @Teepeemm:没错。正如我所说,“您将通过自己解决它来了解更多。”
  • 但是您的答案唯一添加的是明确指出9 * 6 * 4 * 4 * 3 * 3 * 3 * 3 来自素数的幂。除此之外(很明显?)事实,这个答案什么也没增加。
  • @Teepeemm:再读一遍,“从一些小而简单的例子开始。”
  • 我完全同意你的观点,自己弄清楚是最好的学习方式。但是,是什么让您认为 OP 没有尝试任何小而简单的示例?就目前而言,这不是问题的答案,只是评论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-11
  • 1970-01-01
  • 2012-03-27
  • 1970-01-01
相关资源
最近更新 更多