1 条题解

  • 0
    @ 2025-9-15 19:40:38

    题解思路

    这是一道典型的递推动态规划结合组合数学的问题,核心在于通过卡特兰数建模有效的栈操作序列,并利用组合数学计算特定元素贡献的方案数。

    核心算法分析

    问题本质理解: 题目要求计算所有有效操作序列的收益总和。每个有效序列都是一个合法的入栈出栈序列,这正对应于卡特兰数的经典应用场景。

    关键数学建模

    卡特兰数应用: 长度为2n的合法括号序列(入栈出栈序列)的数量为第n个卡特兰数: Cn=1n+1(2nn)C_n = \frac{1}{n+1}\binom{2n}{n}

    组合计数核心: 对于元素a[i]在栈大小为k时被弹出的贡献,需要计算满足以下条件的序列数量:

    1. a[i]入栈前有(i-1)次入栈和(i-k)次出栈
    2. a[i]入栈到出栈之间的子序列是合法的
    3. a[i]出栈后剩余操作构成合法序列

    状态分解策略

    前缀有效序列数

    // p次入栈,q次出栈的有效前缀序列数(栈高度>=0)
    private static long validPrefixCount(int pushCount, int popCount) {
        if (pushCount < popCount) return 0;
        return binomialCoeff[pushCount + popCount][pushCount] - 
               binomialCoeff[pushCount + popCount][pushCount + 1];
    }
    

    后缀完整序列数

    // P个新元素,栈中初始s个元素的完整有效序列数
    private static long validSuffixCount(int newElements, int initialStack) {
        if (newElements < 0) return 0;
        int total = 2 * newElements + initialStack;
        return binomialCoeff[total][newElements] - 
               binomialCoeff[total][newElements + initialStack + 1];
    }
    

    中间子序列处理: 对于a[i]入栈到出栈之间处理t个后续元素的情况,使用卡特兰数: ways(t)=Ct=1t+1(2tt)\text{ways}(t) = C_t = \frac{1}{t+1}\binom{2t}{t}

    详细算法设计

    预计算组合数和卡特兰数

    // 预计算二项式系数
    long[][] binomialCoeff = new long[2*n+1][2*n+1];
    for (int i = 0; i <= 2*n; i++) {
        binomialCoeff[i][0] = 1;
        for (int j = 1; j <= i; j++) {
            binomialCoeff[i][j] = binomialCoeff[i-1][j-1] + binomialCoeff[i-1][j];
        }
    }
    
    // 预计算卡特兰数
    long[] catalan = new long[n+1];
    catalan[0] = 1;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= i; j++) {
            catalan[i+1] += catalan[j] * catalan[i-j];
        }
    }
    

    贡献计算核心逻辑

    long totalContribution = 0;
    
    for (int elementIndex = 1; elementIndex <= n; elementIndex++) {
        for (int stackSize = 1; stackSize <= elementIndex; stackSize++) {
            
            // 计算中间子序列的总方案数
            long intermediateSum = 0;
            for (int processedCount = 0; processedCount <= n - elementIndex; processedCount++) {
                long remainingCount = n - elementIndex - processedCount;
                intermediateSum += catalan[processedCount] * 
                                 validSuffixCount(remainingCount, stackSize - 1);
            }
            
            // 总方案数 = 前缀方案数 × 中间方案数
            long totalWays = validPrefixCount(elementIndex - 1, elementIndex - stackSize) * 
                            intermediateSum;
            
            // 累加贡献
            totalContribution += array_A[elementIndex-1] * array_B[stackSize-1] * totalWays;
        }
    }
    

    复杂度分析

    • 预计算:O(n²)
    • 主循环:O(n³)
    • 总体复杂度:O(n³)
    • 空间复杂度:O(n²)

    Java代码实现

    import java.io.*;
    import java.util.*;
    
    public class Main {
        
        private static long[][] binomialCoefficients;
        private static long[] catalanNumbers;
        
        private static void precomputeCombinatorics(int maxN) {
            int size = 2 * maxN + 1;
            binomialCoefficients = new long[size][size];
            
            // 计算二项式系数 C(n,k)
            for (int i = 0; i < size; i++) {
                binomialCoefficients[i][0] = 1;
                for (int j = 1; j <= i; j++) {
                    binomialCoefficients[i][j] = binomialCoefficients[i-1][j-1] + 
                                               binomialCoefficients[i-1][j];
                }
            }
            
            // 计算卡特兰数
            catalanNumbers = new long[maxN + 1];
            catalanNumbers[0] = 1;
            
            for (int i = 0; i < maxN; i++) {
                catalanNumbers[i + 1] = 0;
                for (int j = 0; j <= i; j++) {
                    catalanNumbers[i + 1] += catalanNumbers[j] * catalanNumbers[i - j];
                }
            }
        }
        
        private static long getBinomialCoeff(int n, int k) {
            if (k < 0 || k > n || n < 0) return 0;
            return binomialCoefficients[n][k];
        }
        
        // 计算有效前缀序列的数量(栈操作序列的前缀,保证栈高度非负)
        private static long computeValidPrefixSequences(int pushOps, int popOps) {
            if (pushOps < popOps) return 0;
            return getBinomialCoeff(pushOps + popOps, pushOps) - 
                   getBinomialCoeff(pushOps + popOps, pushOps + 1);
        }
        
        // 计算完整有效序列的数量(给定初始栈状态和新元素数量)
        private static long computeValidCompleteSequences(int newElementCount, int initialStackSize) {
            if (newElementCount < 0) return 0;
            int totalOperations = 2 * newElementCount + initialStackSize;
            return getBinomialCoeff(totalOperations, newElementCount) - 
                   getBinomialCoeff(totalOperations, newElementCount + initialStackSize + 1);
        }
        
        private static long calculateTotalBenefit(int n, int[] arrayA, int[] arrayB) {
            precomputeCombinatorics(n);
            
            long totalBenefit = 0;
            
            // 遍历每个元素和每个可能的栈大小
            for (int elementPos = 1; elementPos <= n; elementPos++) {
                for (int stackDepth = 1; stackDepth <= elementPos; stackDepth++) {
                    
                    // 计算元素a[elementPos]在栈深度为stackDepth时被弹出的总方案数
                    long totalSchemesSum = 0;
                    
                    // 枚举在该元素入栈到出栈之间处理的后续元素数量
                    for (int intermediateProcessed = 0; intermediateProcessed <= n - elementPos; intermediateProcessed++) {
                        int remainingToProcess = n - elementPos - intermediateProcessed;
                        
                        // 中间处理intermediateProcessed个元素的方案数(卡特兰数)
                        long intermediateWays = catalanNumbers[intermediateProcessed];
                        
                        // 后续处理remainingToProcess个元素的完整方案数
                        long suffixWays = computeValidCompleteSequences(remainingToProcess, stackDepth - 1);
                        
                        totalSchemesSum += intermediateWays * suffixWays;
                    }
                    
                    // 前缀部分的有效方案数
                    long prefixWays = computeValidPrefixSequences(elementPos - 1, elementPos - stackDepth);
                    
                    // 总的有效操作方案数
                    long totalValidSchemes = prefixWays * totalSchemesSum;
                    
                    // 累加该元素的贡献
                    totalBenefit += (long)arrayA[elementPos - 1] * arrayB[stackDepth - 1] * totalValidSchemes;
                }
            }
            
            return totalBenefit;
        }
        
        public static void main(String[] args) throws IOException {
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            
            int n = Integer.parseInt(reader.readLine());
            
            String[] aInput = reader.readLine().split(" ");
            int[] arrayA = new int[n];
            for (int i = 0; i < n; i++) {
                arrayA[i] = Integer.parseInt(aInput[i]);
            }
            
            String[] bInput = reader.readLine().split(" ");
            int[] arrayB = new int[n];
            for (int i = 0; i < n; i++) {
                arrayB[i] = Integer.parseInt(bInput[i]);
            }
            
            long result = calculateTotalBenefit(n, arrayA, arrayB);
            System.out.println(result);
            
            reader.close();
        }
    }
    

    C++代码实现

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    class StackBenefitCalculator {
    private:
        vector<vector<long long>> combinationTable;
        vector<long long> catalanSequence;
        int maxSize;
        
        void initializeCombinatorics(int n) {
            maxSize = 2 * n + 1;
            combinationTable.assign(maxSize, vector<long long>(maxSize, 0));
            
            // 构建二项式系数表
            for (int i = 0; i < maxSize; i++) {
                combinationTable[i][0] = 1;
                for (int j = 1; j <= i; j++) {
                    combinationTable[i][j] = combinationTable[i-1][j-1] + combinationTable[i-1][j];
                }
            }
            
            // 构建卡特兰数序列
            catalanSequence.assign(n + 1, 0);
            catalanSequence[0] = 1;
            
            for (int i = 0; i < n; i++) {
                for (int j = 0; j <= i; j++) {
                    catalanSequence[i + 1] += catalanSequence[j] * catalanSequence[i - j];
                }
            }
        }
        
        long long getCombination(int n, int k) {
            if (k < 0 || k > n || n < 0 || n >= maxSize) return 0;
            return combinationTable[n][k];
        }
        
        // 计算部分栈操作序列的有效数量
        long long countValidPartialSequences(int pushCount, int popCount) {
            if (pushCount < popCount) return 0;
            return getCombination(pushCount + popCount, pushCount) - 
                   getCombination(pushCount + popCount, pushCount + 1);
        }
        
        // 计算完整栈操作序列的有效数量
        long long countValidFullSequences(int newElements, int initialDepth) {
            if (newElements < 0) return 0;
            int totalOps = 2 * newElements + initialDepth;
            return getCombination(totalOps, newElements) - 
                   getCombination(totalOps, newElements + initialDepth + 1);
        }
        
    public:
        long long solve(int n, const vector<int>& valuesA, const vector<int>& valuesB) {
            initializeCombinatorics(n);
            
            long long totalRevenue = 0;
            
            // 枚举每个元素位置和对应的栈深度
            for (int pos = 1; pos <= n; pos++) {
                for (int depth = 1; depth <= pos; depth++) {
                    
                    // 计算该元素在指定深度被弹出时的所有方案
                    long long aggregatedWays = 0;
                    
                    // 枚举中间处理的元素数量
                    for (int midProcessed = 0; midProcessed <= n - pos; midProcessed++) {
                        int laterProcessed = n - pos - midProcessed;
                        
                        // 中间处理方案数(卡特兰数)
                        long long midWays = catalanSequence[midProcessed];
                        
                        // 后续处理方案数
                        long long laterWays = countValidFullSequences(laterProcessed, depth - 1);
                        
                        aggregatedWays += midWays * laterWays;
                    }
                    
                    // 前序处理方案数
                    long long prefixWays = countValidPartialSequences(pos - 1, pos - depth);
                    
                    // 该配置下的总有效方案数
                    long long configurationWays = prefixWays * aggregatedWays;
                    
                    // 累加收益贡献
                    totalRevenue += static_cast<long long>(valuesA[pos - 1]) * 
                                   valuesB[depth - 1] * configurationWays;
                }
            }
            
            return totalRevenue;
        }
    };
    
    int main() {
        ios_base::sync_with_stdio(false);
        cin.tie(nullptr);
        
        int n;
        cin >> n;
        
        vector<int> arrayA(n), arrayB(n);
        
        for (int i = 0; i < n; i++) {
            cin >> arrayA[i];
        }
        
        for (int i = 0; i < n; i++) {
            cin >> arrayB[i];
        }
        
        StackBenefitCalculator calculator;
        long long result = calculator.solve(n, arrayA, arrayB);
        
        cout << result << endl;
        
        return 0;
    }
    

    时间复杂度:O(n³),主要由三重循环决定 空间复杂度:O(n²),用于存储组合数表

    信息

    ID
    294
    时间
    1000ms
    内存
    256MiB
    难度
    6
    标签
    递交数
    1
    已通过
    1
    上传者