组合求和-矩阵连乘所有加括号方式_2023_08_12

文章讨论了如何计算给定矩阵链的加括号方式总数,通过递归和动态规划的方法解决,强调了动态规划在解决重复子问题上的优势。暴力求解虽有高时间复杂度,而动态规划则提供了更有效的方法。

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

矩阵链加括号方式总数

  1. 前言

矩阵链乘积的瓶颈在于其标量运算的次数,不同的结合次序对其时间性能影响远大于矩阵乘积运算本身,可以看到许多教材上把求解矩阵标量运算的最优解作为动态规划的示例,问题隐含动态规划两大特征: 最优子结构及重叠子问题。诸多教材上对此作了详细的描述和解释,此问题本文不再做过多讨论。

本文旨在讨论给定某个矩阵,讨论其不同加括号的方式,要求求出所有可能加括号的数量,并就此问题引出Catalan Number的一般概念。

  1. 问题描述

给定矩阵<M1,M2,…Mn>,探索其可能加括号的方式。为了解决此问题,从最简单形式开始逐步进行研究其解的形式,假定只有一个矩阵M1, 那么其加括号的方式为1。同理,给定两个矩阵,显而易见,其加括号方式总数也为1。那么对于n个矩阵,那么其加括号的总数为几多呢?

为了探讨此一般解问题,假定第 k个矩阵把n个矩阵分两部分,表示第一部分矩阵为<M1,M2,…Mk>,表示第二部分矩阵为<Mk+1,M2,…Mn>。规定P(n)代表n个矩阵所有可能加括号方式的综合,可采用下列递归方程式表示其值,
P ( n ) = ∑ k = 1 n − 1 P ( k ) ∗ P ( n − k ) ;   ( n ≥ 2 ) P(n)=\sum_{k=1}^{n-1}{P(k)*P(n-k)} ;\ (n\geq2) P(n)=k=1n1P(k)P(nk); (n2)
很明显,问题纳入分治的范畴,它之和子问题的长度相关的乘积相关,矩阵本身对其没有影响,P(k)代表k个矩阵可能的加括号方式,P(n-k)代表n-k个矩阵加括号方式,P(k)*P(n-k)代表以k为分界的所有加括号的方式,而P(k)*P(n-k)对于所有的k的方式求和,便是P(n)的值。

  1. 暴力递归方案(无记忆递归)

上述表达式为经典的递归求和方式,可以利用暴力求解途径,对每个n和k分割进行求解,最后求和即可得到最终的结果,它的时间复杂度与求解Catalan number相同(Program for nth Catalan Number - GeeksforGeeks),采用暴力方法求解的时间复杂度为Ω(4n/n3/2),暴力解决方法不是理想求解问题的方式,下一篇幅中将引入动态规划的途径求解。

通过观察发现,n==1的情况下构成递归的基础解,函数直接返回1作为递归结束点。定义 sum为不同加括号的方式,它可以与上级栈的乘积和进行累加。

深入探索就会发现f(n-i)和f(i)递归函数存在可能的重合部分,这将导致每次递归都到出口点,对函数计算构成严重浪费的行为。

int find_matrix_complete_parenthesis_recursion(int n)
{
    int i;
    int sum;

    if(n==1)
    {
        return 1;
    }

    sum=0;
    for(i=1;i<n;i++)
    {
        sum += find_matrix_complete_parenthesis_recursion(n - i) * \
        find_matrix_complete_parenthesis_recursion(i);
    }

    return sum;

}
  1. 动态规划方案

上节讨论展开过程中,发现求解过程存在诸多重复子问题,虽然求和过程未呈现显著的最优子问题特征,原因在于其行为是对不同问题进行求和,求和过程本来就无所谓的最优/最劣的过程,它关注的是加括号方式的不同类型的求和。

int find_matrix_complete_parenthesis_dp(int n)
{
    int i;
    int j;
    int dp[n+1];

    memset(dp,0,sizeof(dp));
    dp[1]=1;

    for(i=2;i<=n;i++)
    {
        for(j=1;j<i;j++)
        {
            dp[i]+=dp[j]*dp[i-j];
        }
    }

    return dp[n];
}
  1. 总结

求解组合总和,一般不涉及到求解最大或最小值的操作,其过程汇总也不涉及到选择的代价,因为需要对所有的可能性选择进行求和汇总。

参考资料

  1. Program for nth Catalan Number - GeeksforGeeks
<think>我们参考用户提供的引用内容,特别是引用[2]和引用[3]中关于使用SUMPRODUCT和COUNTIFS进行去重计数的方法。 用户的需求是:使用COUNTIFS和SUMPRODUCT组合实现筛选去重计数的公式写法。 核心思路: 1. 使用COUNTIFS函数来统计每个组合条件出现的次数。 2. 利用1除以该次数(即1/COUNTIFS(...))来得到每个组合的权重。这样,如果某个组合出现n次,那么每次出现的权重就是1/n,那么n个1/n相就等于1,即这个组合只被计算一次。 3. 然后使用SUMPRODUCT函数将条件区域(筛选条件)与这个权重相乘,并求和。 公式基本结构: =SUMPRODUCT( (条件区域1=条件1) * (条件区域2=条件2) * ... * (1/COUNTIFS(去重条件区域1, 去重条件区域1, 去重条件区域2, 去重条件区域2, ...)) ) 注意:当数据中有空白单元格时,1/COUNTIFS可能会返回错误值(因为分母为0),所以需要上IFERROR函数处理。 具体步骤: 步骤1:确定筛选条件区域和条件值(例如:A列="产品1", C列="在售") 步骤2:确定去重的列(例如:去重的是型号,那么去重条件区域包括型号列,但注意:去重条件区域应该包括所有需要去重的列,以及可能还需要包括筛选条件列?实际上,这里去重计数的是型号,但是因为型号可能在不同的产品下重复,所以我们需要按产品、型号、状态等组合去重?) 引用[2]中的例子: 多条件2(产品1的在售型号有几种?): 公式1:SUMPRODUCT((C2:C18="在售")*(A2:A18="产品1")*1/COUNTIFS(A2:A18,A2:A18,B2:B18,B2:B18,C2:C18,C2:C18)) 解释: 这里去重计数的是型号(B列),但是为了确保每个型号在特定产品(A列)和特定状态(C列)下的唯一性,我们在COUNTIFS中使用了A、B、C三列作为条件。这样,每个型号在相同产品和状态下的多次出现会被合并为一次。 但是,这个公式有一个问题:当同一个型号在相同产品和状态下出现多次时,1/COUNTIFS会为每次出现赋予权重(如出现3次,则每次权重为1/3,最后相为1)。然后,再乘以条件筛选(C列="在售"且A列="产品1"),这样就把满足条件的每个型号的权重相,最后得到去重后的型号数量。 然而,如果数据中有空白单元格,1/COUNTIFS可能会返回#DIV/0!错误(因为如果COUNTIFS结果为0,则分母为0)。因此,需要改进公式,使用IFERROR将错误值转为0。 改进公式(引用[3]中处理空白单元格的方法): =SUMPRODUCT( (条件区域1=条件1) * (条件区域2=条件2) * ... * IFERROR(1/COUNTIFS(去重条件区域1, 去重条件区域1, 去重条件区域2, 去重条件区域2, ...), 0) ) 注意:在Excel中,如果使用整列(如A:A)可能会导致计算缓慢或卡死,所以建议使用具体的数据范围(如A2:A100)。 示例: 假设数据如下: A列:产品(从A2开始) B列:型号(从B2开始) C列:状态(从C2开始) 要求:统计产品为“产品1”且状态为“在售”的不同型号数量。 公式: =SUMPRODUCT( (A2:A100="产品1") * (C2:C100="在售") * IFERROR(1/COUNTIFS(A2:A100, A2:A100, B2:B100, B2:B100, C2:C100, C2:C100), 0) ) 注意:这个公式在Excel中需要按Ctrl+Shift+Enter吗?根据引用[3],在Excel中需要按Ctrl+Shift+Enter(数组公式),但在WPS中不需要。然而,实际上SUMPRODUCT函数本身可以处理数组,所以通常直接按Enter即可。但是,如果公式中包含数组运算,且你的Excel版本较旧,可能需要按Ctrl+Shift+Enter。 但是,根据引用[3]的说明,在包含空白单元格的情况下,他们使用了数组公式(按Ctrl+Shift+Enter)。因此,为了确保兼容性,建议在Excel中按Ctrl+Shift+Enter结束输入(公式会被{}包裹)。不过,在较新的Excel版本中,SUMPRODUCT通常不需要三键结束。 另外,如果去重计数的列(如型号)有空白单元格,那么COUNTIFS会统计空单元格,但1/COUNTIFS会得到1(因为空单元格只出现一次?)或者多次出现则按权重分配。但注意,空字符串也会被视为一个值。如果我们不想统计空值,可以在条件中入非空条件。 例如,排除型号为空的情况: =SUMPRODUCT( (A2:A100="产品1") * (C2:C100="在售") * (B2:B100<>"") * IFERROR(1/COUNTIFS(A2:A100, A2:A100, B2:B100, B2:B100, C2:C100, C2:C100), 0) ) 或者,在COUNTIFS中排除空值?但COUNTIFS的条件区域是动态的,我们无法在COUNTIFS内部排除空值,所以可以在外部乘一个非空条件。 总结步骤: 1. 确定筛选条件:多个条件用乘号(*)连接。 2. 确定去重的列组合(即哪些列的组合需要唯一,这里注意,去重计数的对象是这些列的组合,但我们在公式中是通过给每个组合分配权重来实现去重计数)。 3. 在COUNTIFS中,将去重列组合作为条件区域和条件值(注意:条件区域和条件值范围相同,表示统计每个组合出现的次数)。 4. 用1除以COUNTIFS的结果,并用IFERROR处理错误(将错误转为0)。 5. 用SUMPRODUCT将筛选条件和IFERROR(1/COUNTIFS(...),0)相乘并求和。 注意:这个公式在数据量大的时候可能会比较慢,因为COUNTIFS部分要计算一个数组(每个单元格对应的COUNTIFS值)。 下面我们给出一个通用的公式框架: 通用公式: =SUMPRODUCT( (筛选条件区域1=条件值1) * (筛选条件区域2=条件值2) * ... * IFERROR(1/COUNTIFS( 去重条件区域1, 去重条件区域1, 去重条件区域2, 去重条件区域2, ... ), 0) ) 注意:COUNTIFS中的条件区域必须是相同大小的单列区域,且与筛选条件区域大小一致。 下面我们用一个具体的例子来演示: 数据表(A1:C6): A B C 1 产品 型号 状态 2 产品1 型号1 在售 3 产品1 型号1 在售 4 产品1 型号2 在售 5 产品2 型号1 在售 6 产品1 型号3 停售 要求:统计产品1的在售型号数量(去重)。正确答案应该是2(型号1和型号2)。 公式: =SUMPRODUCT( (A2:A6="产品1") * (C2:C6="在售") * IFERROR(1/COUNTIFS(A2:A6, A2:A6, B2:B6, B2:B6, C2:C6, C2:C6), 0) ) 计算过程: 1. 筛选条件部分: (A2:A6="产品1") -> [TRUE, TRUE, TRUE, FALSE, TRUE] -> [1,1,1,0,1] (C2:C6="在售") -> [TRUE, TRUE, TRUE, TRUE, FALSE] -> [1,1,1,1,0] 相乘: [1*1, 1*1, 1*1, 0*1, 1*0] = [1,1,1,0,0] 2. 计算每个单元格对应的COUNTIFS(相同产品、型号、状态的出现次数): 第2行:产品1,型号1,在售 -> 出现2次(第2行和第3行) -> COUNTIFS结果为2 第3行:同上 -> 2 第4行:产品1,型号2,在售 -> 出现1次 -> 1 第5行:产品2,型号1,在售 -> 出现1次 -> 1 第6行:产品1,型号3,停售 -> 出现1次 -> 1 所以,COUNTIFS数组为:[2,2,1,1,1] 3. 1/COUNTIFS: [1/2, 1/2, 1/1, 1/1, 1/1] = [0.5, 0.5, 1, 1, 1] 用IFERROR包裹,没有错误,所以还是[0.5,0.5,1,1,1] 4. 将第1步的结果数组与第3步的数组相乘: [1*0.5, 1*0.5, 1*1, 0*1, 0*1] = [0.5, 0.5, 1, 0, 0] 5. SUMPRODUCT求和:0.5+0.5+1+0+0=2 结果正确。 但是,如果我们要统计的是不同型号(不管状态),那么去重条件区域应该只包括产品和型号吗?注意,我们的需求是统计产品1的在售型号,但是型号可能会在不同的状态下出现多次(比如同一型号在售和停售都有),但我们的筛选条件已经限制了状态为“在售”,所以去重条件区域应该包括哪些列? 实际上,去重条件区域应该包括我们想要去重的列(即型号列)以及筛选条件列(产品列和状态列)?不,这里注意:我们想要的是型号在产品1且状态在售的情况下去重。所以,去重条件区域应该包括产品、型号、状态。因为同一个型号如果出现在不同的产品下,我们认为是不同的;同样,同一个型号在同一产品下但状态不同(比如在售和停售)我们也认为是不同的记录?但是,我们的需求是只统计在售的,所以同一个型号如果在产品1下同时有在售和停售,我们只关心在售的那条记录。所以,在去重时,我们只考虑在售的记录。因此,去重条件区域应该包括产品、型号、状态,这样就能区分同一个型号在不同状态下的情况。 然而,如果我们只想按型号去重(不管状态),那么就不应该把状态列包括在去重条件中。但是,注意我们的筛选条件已经限制了状态,所以实际上我们只会在状态为在售的记录中计算。那么,如果我们只按产品、型号去重,会怎么样? 公式改为: =SUMPRODUCT( (A2:A6="产品1") * (C2:C6="在售") * IFERROR(1/COUNTIFS(A2:A6, A2:A6, B2:B6, B2:B6), 0) ) 计算过程: 1. 筛选条件数组:[1,1,1,0,0](同上) 2. COUNTIFS(按产品、型号): 第2行:产品1,型号1 -> 出现3次(第2,3,5行?不对,第5行是产品2)-> 第2行:产品1,型号1 -> 出现2次(第2,3行) 第3行:同上 -> 2 第4行:产品1,型号2 -> 出现1次 ->1 第5行:产品2,型号1 -> 出现1次 ->1 第6行:产品1,型号3 -> 出现1次 ->1 所以COUNTIFS数组:[2,2,1,1,1] 3. 1/COUNTIFS: [0.5,0.5,1,1,1] 4. 相乘:[1*0.5, 1*0.5, 1*1, 0*1, 0*1] = [0.5,0.5,1,0,0] 5. 求和:2 结果相同。为什么?因为虽然第5行(产品2,型号1)也被计数了,但是我们的筛选条件中产品1的条件将其过滤掉了(第5行产品2,所以条件为0)。所以,即使我们只按产品和型号去重,因为筛选条件已经限制了状态,所以最终只会在产品1的在售记录中计算。而且,同一个型号(型号1)在产品1中出现了两次(在售),我们通过权重将其合并为1(0.5+0.5)。所以,这个公式也是正确的。 但是,这里有一个关键点:去重条件区域必须至少包括筛选条件中涉及的列(除了状态列?)?实际上,去重条件区域应该包括我们想要去重的列(型号)以及用于区分不同组的列(产品),因为不同产品下的相同型号应该算作不同的型号。而状态列在去重条件中不需要,因为我们的需求是按型号去重(在筛选条件中已经限制了状态)。所以,去重条件区域应该包括产品列和型号列。 因此,我们可以根据需求调整去重条件区域。如果去重计数只针对型号列(不考虑状态),那么去重条件区域就是产品列和型号列。如果去重计数需要同时考虑型号和状态,那么就去重条件区域包括产品、型号、状态。 总结:去重条件区域应该包括所有需要去重的列以及用于区分不同上下文的列(比如产品),但不包括在去重计数中不需要考虑的列(如状态,因为状态已经被筛选条件限制了,而且我们只按型号去重,不同状态下的同一型号视为同一个型号?这取决于业务需求)。 所以,具体问题具体分析。根据用户的需求,如果去重计数的是型号(不区分状态),那么去重条件区域就是产品列和型号列;如果去重计数的是型号和状态的组合,那么就去重条件区域包括产品、型号、状态。 在用户的问题中,要求的是“产品1的在售型号”,所以型号是去重对象,而产品是分组依据(不同产品下相同型号算不同),状态已经被筛选条件限制为“在售”,所以同一型号在相同产品下无论出现多少次在售状态,我们都算作一个型号。因此,去重条件区域应该包括产品列和型号列。 因此,公式可以写为: =SUMPRODUCT( (A2:A100="产品1") * (C2:C100="在售") * IFERROR(1/COUNTIFS(A2:A100, A2:A100, B2:B100, B2:B100), 0) ) 如果需求是去重计数型号和状态的组合,那么公式中COUNTIFS就要上状态列。 最后,我们给出一个通用的公式模板: 问题:如何统计满足多个筛选条件(条件1,条件2,...)的某列(去重列)的去重数量? 注意:去重列可以是单列或多列的组合,但通常我们是指定一列(如型号)作为去重对象,但为了正确去重,我们需要结合其他相关列(如产品)来确保唯一性。 通用公式(去重列是列D,同时需要结合列A、列B作为分组依据): =SUMPRODUCT( (条件区域1=条件1) * (条件区域2=条件2) * ... * IFERROR(1/COUNTIFS( 分组依据列1, 分组依据列1, // 例如产品列 分组依据列2, 分组依据列2, // 例如型号列(即去重列) ... // 其他需要作为分组依据的列 ), 0) ) 注意:分组依据列必须包括去重列以及所有需要用来区分不同组的列(例如,不同产品下的相同型号要区分开,那么分组依据列就包括产品列和型号列)。 如果去重列是单列(比如型号列),那么分组依据列可以包括: - 型号列(必须) - 其他决定型号唯一性的列(比如产品列,因为不同产品的相同型号算不同) 因此,公式中的分组依据列的范围应该根据业务需求来定。 希望这个详细的解释能帮助用户理解如何组合使用COUNTIFS和SUMPRODUCT进行条件筛选并去重计数。</think>### Excel 使用COUNTIFS和SUMPRODUCT实现条件筛选去重计数 #### 核心公式原理 通过`SUMPRODUCT`与`COUNTIFS`组合实现多条件去重计数的核心思路是: **对满足筛选条件的记录,赋予权重值 $ \frac{1}{重复次数} $,求和后即得去重计数**。公式结构如下: ```excel =SUMPRODUCT( (筛选条件区域1=条件1) * (筛选条件区域2=条件2) * IFERROR(1/COUNTIFS( 去重依据列1, 去重依据列1, 去重依据列2, 去重依据列2, ... ), 0) ) ``` #### 分步详解(含示例) 假设数据表结构: - A列:产品 | B列:型号 | C列:状态 需统计:**产品="A" 且 状态="在售" 的不同型号数量** ##### 基础公式(无空白单元格) ```excel =SUMPRODUCT( (A2:A100="A") * (C2:C100="在售") * (1/COUNTIFS( A$2:A$100, A$2:A$100, B$2:B$100, B$2:B$100 )) ) ``` **公式解析**: 1. `(A2:A100="A")*(C2:C100="在售")` → 筛选条件矩阵(满足条件=1,否则=0) 2. `COUNTIFS(...)` → 统计每个型号在对应产品中的出现次数 **关键**:使用绝对引用`$`锁定范围 3. `1/COUNTIFS(...)` → 赋予权重(如型号重复3次,每次权重=1/3) 4. `SUMPRODUCT`求和 → 所有权重相即为去重计数 ##### 增强版(处理空白单元格) ```excel =SUMPRODUCT( (A2:A100="A") * (C2:C100="在售") * IFERROR(1/COUNTIFS( A$2:A$100, A$2:A$100, B$2:B$100, B$2:B$100 ), 0) // 空白单元格返回0 ) ``` **关键改进**: - `IFERROR(... ,0)` 处理空白单元格导致的`#DIV/0!`错误[^3] - 避免使用整列引用(如`A:A`),防止卡顿 #### 执行注意事项 | 环境 | 操作要求 | 公式显示 | |------------|------------------------------|-------------------| | **Excel** | 按`Ctrl+Shift+Enter`三键结束 | `{=...}`带大括号 | | **WPS** | 直接按`Enter` | 无大括号 | #### 应用场景示例 **需求**:统计产品="A"且状态="在售"的不同型号数量 ```excel =SUMPRODUCT( (A2:A100="A") * (C2:C100="在售") * IFERROR(1/COUNTIFS( A$2:A$100, A$2:A$100, B$2:B$100, B$2:B$100 ), 0) ) ``` #### 公式优化技巧 1. **排除空值统计**: ```excel =SUMPRODUCT( (A2:A100="A") * (C2:C100="在售") * (B2:B100<>"") * // 排除空白型号 IFERROR(1/COUNTIFS(...),0) ) ``` 2. **多条件去重**(产品+型号+状态): ```excel =SUMPRODUCT( (A2:A100="A") * (C2:C100="在售") * IFERROR(1/COUNTIFS( A$2:A$100, A$2:A$100, B$2:B$100, B$2:B$100, C$2:C$100, C$2:C$100 // 增状态列 ), 0) ) ``` #### 常见错误解决 - **`#VALUE!`错误**:检查区域大小是否一致 - **结果偏大**:确认COUNTIFS中是否包含所有去重依据列 - **计算卡顿**:将`A$2:A$100`改为具体范围(避免整列引用) > 此方法兼容Excel 2007+版本,通过权重分配实现高效去重[^2][^3] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值