1 条题解
-
0
题解思路
这是一道典型的递推动态规划结合组合数学的问题,核心在于通过卡特兰数建模有效的栈操作序列,并利用组合数学计算特定元素贡献的方案数。
核心算法分析
问题本质理解: 题目要求计算所有有效操作序列的收益总和。每个有效序列都是一个合法的入栈出栈序列,这正对应于卡特兰数的经典应用场景。
关键数学建模:
卡特兰数应用: 长度为2n的合法括号序列(入栈出栈序列)的数量为第n个卡特兰数: Cn=1n+1(2nn)C_n = \frac{1}{n+1}\binom{2n}{n}
组合计数核心: 对于元素a[i]在栈大小为k时被弹出的贡献,需要计算满足以下条件的序列数量:
- a[i]入栈前有(i-1)次入栈和(i-k)次出栈
- a[i]入栈到出栈之间的子序列是合法的
- 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
- 上传者