Find the sum of all the multiples of 3 or 5 below

本文通过一个简单的Java程序,展示了如何计算1000以下所有3或5的倍数之和,避免了3和5共有的倍数被重复计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.


public class Multiple {

/**
* @param args
*/
public static void main(String[] args) {
int sum = 0;
for(int i=3;i<1000;i++){
// 避免3和5的公倍数重复相加求和
if(i%3==0 || i%5==0){
sum += i;
}
}
System.out.println(sum);
}

}
<think>嗯,这个问题看起来有点复杂。我得仔细读题目,确保理解每个步骤。题目是说,有一个数组a,初始所有元素都是白色。Mirzayanov会选择一个或多个不同的索引,把这些位置的元素涂成黑色。然后,所有白色元素中,如果它们的索引是至少一个黑色元素索引的倍数,就会被涂成绿色。得分是所有黑色和绿色元素中的最大值。现在要计算所有可能的选黑元素方式(2^n -1种)的得分之和,模998244353。 首先,我需要理解每个步骤的具体含义。比如,选黑元素的方式是所有的非空子集,共有2^n -1种。对于每一种选法,计算对应的得分,然后把这些得分加起来。最后输出模的结果。 问题的关键在于如何高效地计算所有可能的选法的得分总和。因为直接枚举所有可能的子集显然是不可能的,当n很大时,比如n是1e5级别的话,这样的时间复杂度是无法接受的。所以需要找到一个数学上的优化方法,可能是考虑每个元素作为最大值贡献到总和的次数。 那可能需要每个元素a_i作为最大值的情况出现的次数,然后总和就是a_i乘以次数,再求和。这样,问题就转化为如何计算每个元素a_i在多少种选法中成为最大值。 那么,如何确定a_i在某种选法中是否是最大值?假设当前选法中,a_i被选中或者未被选中,但此时它必须是黑色或绿色的,并且在所有黑色和绿色元素中是最大的。 但这里有个问题:a_i是否是黑色或绿色的,取决于选中的黑色元素的位置。例如,如果选中的某个黑色元素的索引j是i的因数,那么i会被涂成绿色。或者如果i被选中为黑色,那么自然也是黑色。 所以,a_i成为得分的条件是该选法中,a_i是黑色或绿色,并且在所有黑色和绿色的元素中,没有比它更大的元素存在。 这看起来有点复杂,因为需要考虑其他元素的情况。或许,我们可以按元素从大到小处理,计算每个元素作为最大值的贡献次数。例如,对于元素a_i,如果它在某种选法中是最大值,那么所有比它大的元素都不能出现在黑色或绿色中。或者更准确地说,所有比a_i大的元素要么不在黑色或绿色中,或者即使存在,也不被选为最大值的情况。这可能需要容斥或者某种排列组合的技巧。 不过,可能更有效的方法是,按元素从大到小的顺序处理。假设我们按a_i从大到小排序,对于每个元素a_i,计算有多少种选法使得该元素是最大值,并且这是第一次出现这个最大值的情况。这样,可以确保每个选法只被最大的元素统计一次。 具体来说,假设我们已经处理了所有比a_i大的元素,现在处理a_i。此时,我们需要找出所有选法,其中选中的黑色元素满足:在i的因数中至少有一个被选为黑色(这样i会被染成绿色或者本身就是黑色),而所有比a_i大的元素j,它们的下标j所在的因数没有被选为黑色(否则j会被染成绿色或者黑色,这样它们的a_j会比a_i大,所以这种情况下a_i不能成为最大值)。 这似乎可行。那么,对于每个a_i,我们需要计算满足以下条件的选法数目: 1. 在i的某个因数k(包括i自己)中,至少有一个被选为黑色元素。这样,i会被涂成黑色或绿色。 2. 对于所有j>i(假设已经按a从大到小排过序,所以这里的j对应的a_j >= a_i的情况已经被处理过了),如果j的位置被某个选中的k的倍数所影响,则j会被涂成绿色或黑色。因此,在选法中,必须确保这些j的位置不会被选中,或者即使被选中,他们的因数没有被选中的情况,从而导致这些j的位置不会被染成绿色或黑色。 或者,可能更简单的方式是,当处理到a_i时,假设所有比它大的元素都不满足成为最大值的条件,所以这些元素所在的选法中,它们的位置必须无法被染成黑色或绿色。或者,或者说,在那些选法中,这些更大的元素既没有被选为黑色,也没有被它们的某个因数选为黑色,这样它们不会被染成绿色,从而不会出现在黑色或绿色集合中。这样,i就可以成为最大值。 这可能需要一些数学的处理方法。例如,对于每个元素i,统计有多少个选法满足: - i被染成黑色或绿色(即存在至少一个k是i的因数,且k被选为黑色)。 - 对于所有元素j>i(按a的值从大到小排序后的顺序),j没有被染成黑色或绿色,也就是说,对于每个j>i,j的因数中没有任何一个被选为黑色。 同时,选法必须非空,即至少选一个黑色元素。 假设我们按元素的a值从大到小排序,这样在处理第i个元素时,前面的元素的a值更大,已经被处理过。那么,当计算当前元素i的贡献时,必须保证所有比它大的元素j(即已经处理过的元素)都不能被染成绿色或黑色。这要求,这些j的位置的因数中没有被选中的黑色元素。同时,i的因数中至少有一个被选中,并且当前选法的黑色元素必须满足这两个条件。 那么,对于元素i来说,其贡献次数等于满足以下条件的选法数目: S = {选法的子集B,其中B是选中的黑色元素集合} 满足: 1. B非空。 2. B中存在至少一个元素k,使得k是i的因数。 3. 对于每个j(对应的a_j > a_i),所有j的因数都没有被选为B中的元素。即,对于这些j,他们的所有因数都不在B中。这样,j不会被染成绿色或黑色,所以不会被考虑在得分中。 然后,所有这样的选法数目S_i,乘以a_i,就是i的总贡献。然后将所有i的贡献相加得到最终答案。 现在的问题是,如何计算每个i的S_i的值。 这里,我们需要先按a_i从大到小排序元素。因为只有当更大的元素都没有被选中的情况,当前元素i才能作为最大值。 假设我们按a_i从大到小的顺序处理每个元素i。对于每个i,我们需要计算满足上述条件的选法数目。这可能涉及到容斥原理或动态规划的方法。 例如,当处理到i时,所有比i大的元素j已经被处理过,并且我们可能需要确保选法中的黑色元素不包含这些j的任何因数。或者说,对于这些更大的元素j来说,它们的因数集合中的元素都不能被选为黑色元素。 这似乎很复杂,但或许可以将问题分解为: 对于每个i,我们需要计算选法的数目,这些选法满足: - 至少选一个i的因数作为黑色元素。 - 不选任何属于“禁止集合”F_i的元素。这里的F_i是所有比i大的元素j的因数的集合。或者说,F_i中的元素是那些属于某个j>i的因数的元素。因为这些元素一旦被选为黑色,就会导致j被染成绿色或黑色,而j的a值更大,所以i不能成为最大值。 因此,F_i = {k | 存在j>i,k是j的因数}。 那么,对于i来说,选法必须是选非空子集,满足: - 至少有一个元素属于D_i(D_i是i的因数集合,包括i自己)。 - 所有选中的元素必须不属于F_i,且属于D_i的某个部分? 或者,更准确地说,选中的黑色元素必须属于D_i,且不属于F_i?或者可能不是这样? 或者,选中的元素必须满足: 选中的元素集合B必须满足: B ∩ F_i = ∅,并且 B ∩ D_i ≠ ∅. 因为,如果B中的元素k属于F_i,那么k是某个j>i的因数,因此j会被染成绿色或黑色,导致j的a_j比a_i大,所以i不能成为最大值。因此,必须保证B中的元素都不在F_i中。同时,B必须至少有一个元素属于D_i,这样i会被染成绿色或黑色。 所以,问题转化为:对于每个i,计算满足B ∩ F_i = ∅,且 B ∩ D_i ≠ ∅的非空子集B的数目。然后,所有这样的B对应的选法数目就是S_i。而总贡献是a_i * S_i。 但这里的F_i和D_i如何定义? F_i是所有会导致比i大的元素j被染色的元素的集合。也就是说,F_i中的元素k是那些存在j>i(按a的值排序后的顺序)的情况,其中k是j的因数。因此,F_i = {k | ∃j>i,k是j的因数}。 而D_i是i的所有因数的集合,即k是i的因数的元素k的集合。这样,当B中的一个元素k属于D_i时,i会被染色。 所以,对于每个i,计算满足以下条件的非空子集B的数目: B中的元素都是不在F_i中的元素,并且至少有一个元素在D_i中。 这应该等于(总共有S个元素不在F_i中,其中有多少个非空子集B满足至少包含一个D_i的元素)。 所以,假设总共有M_i个元素不在F_i中,其中X_i个属于D_i。那么满足条件的数目是:总共有子集数为 (2^{X_i} -1) * 2^{M_i - X_i} }。因为必须选至少一个元素在D_i中,剩下的元素可以在不在D_i的非F_i元素中任选。 但是这可能不正确,因为D_i中的元素可能和F_i中的元素有重叠?比如,假设D_i中的某个元素k,k是否属于F_i? 这要看情况。例如,i的一个因数k,是否属于F_i。F_i中的元素是那些作为某个j>i的因数的元素。如果i的因数k同时也是某个j>i的因数,那么k属于F_i。此时,这个k虽然属于D_i,但也在F_i中,因此这样的k不能被选为黑色元素,否则会导致j被染色。 因此,D_i中的元素k是否属于F_i?只有当存在j>i(按a的顺序)且k是j的因数时,k才属于F_i。因此,D_i中的元素可能部分属于F_i,部分不属于。 所以,正确的条件是:选中的元素必须属于D_i且不属于F_i,并且至少有一个这样的元素。或者,选中的元素必须全部不属于F_i,并且至少有一个属于D_i。 这样,正确条件是: B ∩ F_i = ∅,并且 B ∩ D_i ≠ ∅,且 B非空。 那么,我们需要计算所有非空子集B,满足: 1. B中的元素都不在F_i中。 2. B中的元素至少有一个属于D_i。 那么,假设在全集U中,元素分为两部分: A:属于D_i且不属于F_i的元素。 B:不属于D_i且不属于F_i的元素。 那么,满足条件的子集B必须包含至少一个A中的元素,并且可以包含任意多个B中的元素。因此,总共有: (2^{|A|} -1) * 2^{|B|} 这样的子集数目。其中,A是D_i中不在F_i中的元素的数量,B是其他不在F_i中的元素的数量。 但这里的全集U是所有的元素,而F_i是那些元素属于F_i的情况。所以,总共有M_i = 总元素数目减去F_i的元素数目。即,M_i = n - |F_i|。但具体的A和B的数目可能比较复杂。 不过,这个问题可能需要更高效的方法。例如,预处理每个i的D_i和F_i的交集,得到D_i’ = D_i \ F_i。也就是,所有属于D_i且不属于F_i的元素的集合。然后,剩下的不在F_i中的元素是U’ = [1..n] \ F_i。要选非空子集,其中至少一个元素属于D_i’,并且其他元素属于U’但不能属于D_i’。 这样,符合条件的子集数目等于: (2^{|D_i’|} -1) * 2^{ |U’| - |D_i’| } } 其中,U’是总共有S = (n - |F_i|)个元素。那么,|U’| - |D_i’| = S - |D_i’| = (n - |F_i|) - |D_i’|. 所以,总共有: (2^{d} -1) * 2^{ (s - d) } 其中,d是D_i’的大小,s是S = n - |F_i|。 但问题是,如何计算每个i的d和s? 这可能比较困难,因为对于每个i,需要找出哪些元素k属于D_i’(即k是i的因数,且k不在F_i中)。 而F_i是那些k属于某个j>i的因数的元素的集合,这里的j是已经处理过的元素(因为我们是按a从大到小排序的)。 这里可能需要一个预处理步骤: 1. 首先,将数组元素按a的值从大到小排序,并记录它们的原始索引。例如,将元素排序后,得到一个数组sorted,其中每个元素包含原索引和a的值。假设排序后的顺序是v_1 >= v_2 >= ... >= v_n,对应的原索引为p_1, p_2, ..., p_n。 2. 对于每个i(这里的i是排序后的索引),对应的原始元素是p_i。我们需要计算当处理到i时,所有比i大的元素(也就是在排序后的数组中i的前面的元素)的因数集合的并集,得到F_i。即,F_i是那些k,存在j在排序后的数组中的前i-1个元素,且k是p_j的因数。 这可能很耗时,因为对于每个i,要计算所有j < i(因为排序是降序的,原数组中更大的元素排在前面)对应的元素的因数,并把这些因数加入F_i。 但如何高效地维护F_i呢? 可以考虑,从最大的元素开始处理,维护一个集合,记录所有已经被处理过的元素的因数。例如,当处理到第i个元素时,F_i就是这个集合中的所有因数。然后,处理完第i个元素后,将当前元素的因数加入这个集合中。 例如,初始时,F是一个空集合。处理顺序是i从1到n(按a的降序排列): 对于i=1,处理最大的元素p_1。此时,F_i是空集,因为还没有处理过任何比它大的元素。然后,将p_1的所有因数加入F集合。 对于i=2,处理第二大的元素p_2。此时,F集合中的元素是所有比它大的元素的因数(也就是p_1的因数)。处理完p_2后,将p_2的因数加入F集合。 依此类推。这样,当处理第i个元素时,F集合中存储的是所有已处理过的元素(即j < i的元素)的因数的并集。这样,F_i就是这个集合。 这样,我们就可以动态维护F集合,从而得到每个i的F_i。 然后,对于每个i的原始索引p_i,D_i是p_i的所有因数的集合。而D_i’是D_i中不在F_i中的元素。也就是,这些因数k必须满足k没有被包含在F集合中,即没有被之前的元素的因数覆盖。 此时,D_i’的大小等于D_i的大小减去D_i与F_i的交集的大小。 这样,我们可以预先处理每个原始索引的所有因数,然后在处理时动态维护F集合,记录哪些k已经被包含在F集合中。 现在的问题转化为: - 如何预处理每个数的因数? - 如何高效维护F集合,以便快速查询某个k是否在F中? 假设n是1e5级别,那么预处理每个数的因数需要O(n log n)的时间,比如用筛法。 而维护F集合可以用一个布尔数组,标记每个k是否已经被加入过F。例如,当处理元素p_i时,遍历p_i的所有因数k,如果k未被标记,则将它们加入F集合,并标记为true。 但这样可能会重复处理因数。例如,如果两个不同的元素有相同的因数k,那么在处理第一个元素时标记k为true,之后处理其他元素时,k已经在F中。 所以,维护F集合可以用一个布尔数组in_F,初始为false。在处理每个元素p_i时,遍历它的所有因数k。如果k未被标记为in_F[k] = true,则将其标记为true,并将其加入F集合。这样,在后续的处理中,这些因数已经被视为在F集合中。 这样,处理完所有比当前元素大的元素后,F集合中的元素就是所有可能被选中的因数导致更大的元素被染色。 那么,对于当前处理的元素i(原索引p_i),D_i是它的所有因数。而D_i’就是其中未被标记为in_F的那些因数。所以,D_i’的大小等于D_i中未被in_F标记的因数的数量。 同时,U’是总共有多少元素未被in_F标记。因为U’ = n - F的大小,而F的大小可以通过维护一个变量,例如count_F,每次新增未被标记的因数时,count_F增加相应的数目。 或者,更简单地说,当处理到i时,U’中的元素数目是n - count_F,其中count_F是当前in_F中的元素数目。但此时,当处理i的因数时,可能已经将某些k加入F中,所以需要动态计算。 或者,U’的元素数目等于总元素数目n减去当前in_F中的元素数目。因为in_F中的元素数目是当前F集合的大小。 所以,在处理元素i时,s = n - count_F(count_F是此时in_F的大小)。 而d是D_i’的元素数目,即D_i中的因数k未被in_F标记的数量。 此时,计算每个i的贡献: 贡献为a_i * ( (2^{d} - 1) * 2^{ (s - d) } ) 然后将这些贡献累加起来。 现在,问题是如何高效地计算这些数值,并处理大数的指数取模问题。 此外,需要注意的是,对于每个i,处理完之后需要将它的所有因数k中未被in_F标记的加入F,并更新count_F。例如,在处理i时,遍历其所有因数k,如果in_F[k]是false,就将它标记为true,并将count_F增加1。 这样,就能维护F集合的正确性。 接下来,具体的步骤: 1. 预处理每个数的因数。例如,对于每个数x,找出其所有因数。这可以通过预处理,比如对于每个数x,遍历它的倍数,或者用更高效的方法。 2. 将原数组按照a的值从大到小排序,同时记录原始索引。 3. 初始化in_F数组为false,count_F为0。 4. 按排序后的顺序处理每个元素: a. 对于当前元素的原始索引p_i,计算其所有因数D_i。 b. 计算d:D_i中未被in_F标记的元素数目。 c. 计算s = n - count_F. d. 贡献为a_i * ( (2^d -1) * 2^{s -d} ) mod MOD. e. 将贡献加到总和中。 f. 遍历D_i中的因数k,如果in_F[k]为false,则将其标记为true,count_F增加1。 这样,每个元素i的因数被处理后,后续的元素在处理时会自动排除这些因数的可能。 现在需要考虑如何处理因数的计算。例如,对于x,其因数包括1到x的所有能整除x的数。这可以通过预处理,比如对每个x,存储其所有因数。 预处理因数的方法: 可以预先为每个数x,遍历其倍数,并记录因数。例如,对于每个数d从1到n,将d加入到其倍数的因数列表中。这样时间复杂度是O(n log n)。 例如,对于d从1到n: for (int x = d; x <= n; x +=d) { factors[x].push_back(d); } 这样,每个x的因数列表factors[x]将包含所有d能整除x的数。 这样预处理因数的时间是O(n log n),这在n=1e5时是可行的。 接下来,按照排序后的顺序处理每个元素。假设原数组是a数组,每个元素的值和索引分别为a_val和index。排序后得到一个数组sorted,其中每个元素包含原索引和a_val,按a_val降序排列。 然后,对于每个sorted[i],处理其原始索引p_i。 现在,计算d的步骤是: 遍历factors[p_i]中的每个因数k,检查in_F[k]是否为false。统计这些k的数量,即d。 然后,计算贡献。 同时,在处理完当前元素后,需要将这些因数k中未被标记的加入F集合,并更新count_F。 例如: for each k in factors[p_i]: if (!in_F[k]): in_F[k] = true count_F +=1 这可能会多次处理同一个因数k,但通过检查in_F[k]的状态,可以避免重复处理。 这样,整个过程的时间复杂度是O(n log n + n log MOD),其中n log n来自预处理因数和排序,而处理每个元素的因数的时间也是O(n log n),因为每个元素的因数数目平均是O(log n)。另外,计算幂的时候需要用快速幂,但由于指数可能很大,可以预处理幂数组,或者每次计算时用快速幂。 但这里需要注意,对于每个元素i,我们需要计算2^d和2^(s-d),其中s =n - count_F。这里的d是当前步骤的d,而s是当前的n - count_F。因此,每次需要计算这两个指数。 这里,可以预处理一个幂数组pow2,其中pow2[i] = 2^i mod MOD。预处理到n的大小即可。 例如,预处理pow2[0..n],其中pow2[i] = 2^i mod MOD。这样,计算的时候直接取pow2[d]即可。 预处理pow2数组的时间是O(n),这在n=1e5时是可行的。 因此,总的步骤: 预处理: 1. 预处理每个数的因数列表。 2. 预处理pow2数组,pow2[i] = 2^i mod MOD。 处理阶段: 3. 将原数组按a的值降序排序,得到sorted数组。 4. 初始化in_F数组为false,count_F=0,总和ans=0。 5. 遍历sorted数组中的每个元素: a. 取出当前元素的原始索引p_i和值a_i。 b. 遍历p_i的所有因数,统计其中未被in_F标记的数目d。 c. s = n - count_F. d. 计算term = a_i * ( (pow2[d] -1) * pow2[s -d] % MOD ) % MOD. e. ans = (ans + term) % MOD. f. 对于p_i的每个因数k,如果in_F[k]为false,则设置为true,并count_F +=1. 这样,整个过程的时间复杂度是O(n log n),因为每个数的因数数目是O(log n)级别的,而其他步骤都是线性的。 现在,需要处理可能的重复因数。例如,当p_i的因数k已经被之前的元素处理过,那么此时不会被统计到d中。因此,只有未被处理的因数才会被计入d,从而确保后续的选法不会导致更大的元素被染色。 这样,这个算法应该是正确的。 现在,需要验证这个算法的正确性。 例如,考虑一个简单的测试用例: n=2,a=[3,5]. 原索引是1和2。假设排序后的顺序是第二个元素(a=5),然后是第一个元素(a=3)。 处理第一个元素(原索引2,a=5): 此时,F集合初始为空。p_i=2的因数是1,2. 统计d=2(因为这两个因数都不在F中)。 s = 2 - 0 =2. term =5 * ( (2^2 -1)*2^(2-2) ) =5*(3*1)=15. 然后,将因数1和2加入F集合,count_F变为2. 处理第二个元素(原索引1,a=3): 它的因数是1和1的因数,即1. 此时,检查因数1是否在F中。是的,所以d=0. 所以,必须选至少一个因数,但是d=0,所以无法满足条件。所以term=0. 总和是15. 而所有可能的选法共有2^2 -1=3种: 选法1:选{1}。黑色元素是1。绿色的元素是那些索引是1的倍数的元素,即1和2。所以黑色和绿色的是1和2。最大值是5(元素2的值)。 选法2:选{2}。黑色元素是2。绿色的元素是2的倍数,即2。所以最大值是5. 选法3:选{1,2}. 黑色元素是1和2. 绿色元素是1的倍数(1,2)和2的倍数(2). 所以最大值是5. 所以总和是5+5+5=15,与算法的结果一致。 另一个测试用例:n=1。原数组只有一个元素a_1=10。此时,只有一种选法:选{1}。得分是10。总和是10. 算法处理: 排序后的元素是原索引1。处理时,因数1不在F中(F初始为空),d=1。s=1-0=1. term=10*( (2^1-1)*2^{0})=10*1*1=10. 正确。 再一个测试用例:n=3,a=[2,3,1]. 原索引1、2、3。假设排序后的顺序是2(a=3),然后是1(a=2),然后是3(a=1)。 处理第一个元素(原索引2,a=3)的因数是1和2。d=2。s=3-0=3. term=3*(3-1)*2^{3-2} =3*(3-1=3)?不,计算式是: (2^d -1) * 2^{s-d} = (2^2 -1)*2^{3-2} =3 *2=6. term=3*6=18. 此时,将因数1和2加入F集合,count_F=2. 处理第二个元素(原索引1,a=2)的因数是1。此时,因数1已经在F中,所以d=0。所以无法满足条件,term=0. 处理第三个元素(原索引3,a=1)的因数是1,3。因数1在F中,因数3不在。所以d=1。此时,s=3-2=1. (2^1-1)*2^{1-1}=1*1=1. term=1*1=1. 总和是18+0+1=19. 现在,手动计算所有可能的选法: 共有7种选法: 选法: 1. {1}: 黑色元素是1。绿色元素是1的倍数:1,2,3。最大值是3(元素2的a值?原索引是1的a是2,原索引2的a是3? 原数组是 [2,3,1]. 原索引1是a=2,原索引2是a=3,原索引3是a=1. 所以,当选{1},黑色元素是1。绿色元素是1的倍数(所有元素,因为1的倍数是所有数)。所以,所有元素都是绿色或黑色。最大值是3(元素2的值)。 选法2:选{2}。黑色元素是2。绿色元素是2的倍数:2. 所以黑色和绿色元素是2。最大值是3. 选法3: 选{3}。黑色元素是3。绿色元素是3的倍数:3. 最大值是1. 选法4: 选{1,2}。黑色元素是1和2。绿色元素是1的倍数(所有)和2的倍数(2, ...). 所以全部元素。最大值是3. 选法5: 选{1,3}。绿色元素是1的倍数(所有)和3的倍数(3). 所以全部元素。最大值是3. 选法6: 选{2,3}。绿色元素是2的倍数(2)和3的倍数(3). 黑色元素是2和3. 所以黑色和绿色是2,3. 最大值是3和1中的最大3. 选法7: 选{1,2,3}。同选法4,最大值是3. 所以各选法的得分为:3,3,1,3,3,3,3。总和是3+3+1+3+3+3+3=19,与算法结果一致。 这说明算法是正确的。 综上,算法的思路是正确的。现在需要将其转化为C++代码。 代码步骤: 1. 预处理因数列表。 2. 预处理pow2数组,计算2^i mod MOD. 3. 将数组元素按值降序排序,保留原索引。 4. 遍历排序后的元素,计算每个的贡献。 需要注意的细节: - 原题中的数组索引是1-based还是0-based?原问题中的数组是a_1到a_n,所以是1-based。 - 预处理因数时,需要处理每个数的所有因数,包括1和自身。 代码实现: 首先,预处理因数: vector<vector<int>> factors(n+1); for (int d=1; d<=n; d++){ for (int x=d; x<=n; x +=d){ factors[x].push_back(d); } } 然后,预处理pow2数组: vector<int> pow2(n+1); pow2[0] = 1; for (int i=1; i<=n; i++){ pow2[i] = (pow2[i-1] * 2) % MOD; } 然后,将原数组排序: vector<pair<int, int>> sorted; // (a_val, index) for (int i=0; i<n; i++){ sorted.emplace_back(a[i], i+1); // assuming a is 0-based in code, but indices are 1-based. } sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) { return a.first > b.first; }); 然后,初始化in_F数组: vector<bool> in_F(n+1, false); int count_F = 0; int ans =0; MOD = 998244353; 处理每个元素: for (auto& [val, idx] : sorted) { // 计算d: number of factors of idx not in F. int d =0; for (int k : factors[idx]){ if (!in_F[k]) d++; } int s = n - count_F; if (d ==0) { // 无法满足条件,贡献为0 continue; } int term = (pow2[d] -1 + MOD) % MOD; // 防止负数 term = (term * 1LL * pow2[s -d]) % MOD; term = (val * 1LL * term) % MOD; ans = (ans + term) % MOD; // 将因数k标记为in_F,并更新count_F for (int k : factors[idx]) { if (!in_F[k]) { in_F[k] = true; count_F++; } } } 最终,输出ans。 需要注意的是,当d=0时,无法选择任何因数,因此贡献为0,不需要处理。此外,pow2[d] -1可能为负数,所以要加MOD后再取模。 这样,代码就完成了。现在测试一下前面的测试用例。 例如,n=2,a数组是35。原索引1是3,原索引2是5。排序后是5(索引2),3(索引1)。 处理索引2: 因数1和2。此时in_F都是false。d=2. s=2-0=2. term=5*(3 * 1)=15. 然后标记1和2为true,count_F=2. 处理索引1:因数1。此时in_F[1]是true。d=0. 跳过。 总和是15. 正确。 另一个测试用例n=3,数组是2,3,1。原索引是1(a=2),2(a=3),3(a=1)。排序后的顺序是3(索引2)、2(索引1)、1(索引3)?或者原数组的顺序? 原数组的索引是1-based。在代码中,假设原数组的存储是0-based,但在sorted数组中保存的是原索引+1。例如,原数组是[2,3,1],则索引1、2、3对应的值分别是2、3、1。排序后的顺序是3(索引2),2(索引1),1(索引3)? 不,sorted数组是按a_val降序排列。原数组是: 元素0: 2 →索引1 元素1:3 →索引2 元素2:1 →索引3. 所以 sorted数组将按值排序为3(索引2,值3),2(索引1,值2),1(索引3,值1)。 处理第一个元素(val=3,idx=2)的因数是1,2。d=2,s=3-0=3. term=3 * ( (3)*2^(1) ) → (2^2-1)=3, 2^(3-2)=2 → 3*2=6 → term=3*6=18. 然后标记1和2,count_F=2. 处理第二个元素(val=2,idx=1)的因数是1。此时,in_F[1]是true,所以d=0. 跳过。 处理第三个元素(val=1,idx=3)的因数是1,3。其中,1已标记,3未标记。d=1. s=3-2=1. term=1*(1 * 1)=1. 标记3,count_F=3. 总和是18+1=19. 正确。 综上,代码是正确的。</think>要解决这个问题,我们需要计算所有可能的选法下,数组元素作为黑色或绿色元素的最大值的总和。通过分析,我们可以将问题转化为每个元素作为最大值时的贡献次数,并利用数学方法优化计算。 ### 方法思路 1. **预处理因数**:对于每个数,预处理其所有因数。 2. **预处理幂数组**:计算2的幂次,便于后续快速计算组合数。 3. **排序数组**:按元素值从大到小排序,处理每个元素时确保更大的元素已被处理。 4. **动态维护禁止集合**:记录已处理元素的因数,避免后续元素因这些因数被染色。 5. **计算贡献**:对于每个元素,计算其作为最大值的有效选法数目,并累加贡献。 ### 解决代码 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; const int MOD = 998244353; int main() { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; } // 预处理每个数的因数 vector<vector<int>> factors(n + 1); for (int d = 1; d <= n; ++d) { for (int x = d; x <= n; x += d) { factors[x].push_back(d); } } // 预处理2的幂次 vector<int> pow2(n + 1); pow2[0] = 1; for (int i = 1; i <= n; ++i) { pow2[i] = (pow2[i - 1] * 2) % MOD; } // 按a的值降序排序,保留原索引(1-based) vector<pair<int, int>> sorted; for (int i = 0; i < n; ++i) { sorted.emplace_back(a[i], i + 1); } sort(sorted.begin(), sorted.end(), [](const auto& x, const auto& y) { return x.first > y.first; }); vector<bool> in_F(n + 1, false); // 标记是否在F集合中 int count_F = 0; // F集合的大小 int ans = 0; for (const auto& [val, idx] : sorted) { // 计算d:当前idx的因数中不在F中的数量 int d = 0; for (int k : factors[idx]) { if (!in_F[k]) { ++d; } } int s = n - count_F; // 剩余可用元素的数量 if (d == 0) { continue; // 无法选择任何因数,贡献为0 } // 计算贡献 int term = (pow2[d] - 1 + MOD) % MOD; term = (term * 1LL * pow2[s - d]) % MOD; term = (val * 1LL * term) % MOD; ans = (ans + term) % MOD; // 将当前idx的因数加入F集合 for (int k : factors[idx]) { if (!in_F[k]) { in_F[k] = true; ++count_F; } } } cout << ans << endl; return 0; } ``` ### 代码解释 1. **预处理因数**:使用筛法预处理每个数的因数,时间复杂度为O(n log n)。 2. **预处理幂数组**:计算2的幂次,便于后续快速计算组合数。 3. **排序数组**:按元素值从大到小排序,确保处理每个元素时更大的元素已被处理。 4. **动态维护禁止集合**:使用布尔数组`in_F`记录已处理元素的因数,避免后续元素因这些因数被染色。 5. **计算贡献**:对于每个元素,计算其作为最大值时的有效选法数目,并累加贡献,结果取模处理。 这种方法通过预处理和动态维护禁止集合,高效地计算了所有可能的选法下的最大值总和,确保了时间复杂度的可行性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值