UVa 12143 Stopping Doom‘s Day
题目描述
宇宙的末日即将在 555 小时后到来,为了阻止末日,你需要计算一个复杂表达式的值 TTT:
T=(∑i=1n∑j=1n∑k=1n∑l=1n∑m=1n∣i−j∣⋅∣j−k∣⋅∣k−l∣⋅∣l−m∣⋅∣m−i∣)%10007 T = \left( \sum_{i=1}^{n} \sum_{j=1}^{n} \sum_{k=1}^{n} \sum_{l=1}^{n} \sum_{m=1}^{n} |i-j| \cdot |j-k| \cdot |k-l| \cdot |l-m| \cdot |m-i| \right) \% 10007 T=(i=1∑nj=1∑nk=1∑nl=1∑nm=1∑n∣i−j∣⋅∣j−k∣⋅∣k−l∣⋅∣l−m∣⋅∣m−i∣)%10007
输入包含最多 100010001000 行,每行一个整数 nnn(0<n≤2×1090 < n \leq 2 \times 10^90<n≤2×109),以 000 结束。对于每个 nnn,输出对应的 TTT 值。
解题思路
问题分析
这是一个五重循环的求和问题,直接计算的时间复杂度为 O(n5)O(n^5)O(n5),对于 nnn 最大可达 2×1092 \times 10^92×109 的情况,完全不可行。
关键观察
111. 对称性分析:表达式中的五个变量 iii, jjj, kkk, lll, mmm 形成一个环状结构,具有高度的对称性。这种对称性意味着结果可能是关于 nnn 的多项式函数。
222. 多项式性质:通过数学推导(或小规模暴力计算验证),可以确定 T(n)T(n)T(n) 是一个关于 nnn 的 101010 次多项式。这是因为表达式涉及 555 个绝对值的乘积,每个绝对值项最多贡献 222 次。
333. 模运算优势:题目要求结果对 100071000710007 取模,而 100071000710007 是一个质数,这为我们在模意义下进行多项式插值提供了便利。
核心算法:拉格朗日插值
算法原理
对于 ddd 次多项式,如果知道 d+1d+1d+1 个点的函数值,就可以唯一确定这个多项式。拉格朗日插值公式为:
P(x)=∑i=0dyi∏j≠ix−xjxi−xj P(x) = \sum_{i=0}^{d} y_i \prod_{j \neq i} \frac{x - x_j}{x_i - x_j} P(x)=i=0∑dyij=i∏xi−xjx−xj
具体实现步骤
111. 预处理小规模数据:通过暴力计算或其他方法得到 n=1n = 1n=1 到 111111 时的正确 T(n)T(n)T(n) 值(模 100071000710007)。
222. 建立插值基点:使用这 111111 个点作为已知数据点。
333. 处理查询:
- 如果 n≤11n \leq 11n≤11,直接返回预计算的结果
- 如果 n>11n > 11n>11,使用拉格朗日插值计算结果
模运算处理
在模 100071000710007 下进行插值时需要注意:
- 所有运算都要对 100071000710007 取模
- 除法要转换为乘以模逆元
- 使用扩展欧几里得算法计算模逆元
算法复杂度分析
- 预处理:O(1)O(1)O(1),只需要 111111 个点的值
- 每次查询:O(k2)O(k^2)O(k2),其中 k=11k = 11k=11 是插值点数量
- 总体复杂度:O(1000×112)O(1000 \times 11^2)O(1000×112),完全可行
为什么选择 111111 个点?
因为 T(n)T(n)T(n) 是 101010 次多项式,根据代数基本定理,需要 111111 个点才能唯一确定一个 101010 次多项式。
代码实现
// Stopping Doom's Day
// UVa ID: 12143
// Verdict: Accepted
// Submission Date: 2025-10-10
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <bits/stdc++.h>using namespace std;const int MOD = 10007; // 题目要求的模数/*** 使用扩展欧几里得算法计算模逆元* @param a 需要求逆元的数* @param m 模数* @return a 在模 m 下的逆元*/
int modularInverse(int a, int m) {int originalMod = m;int y = 0, x = 1;if (m == 1) return 0;while (a > 1) {int quotient = a / m;int temp = m;m = a % m;a = temp;temp = y;y = x - quotient * y;x = temp;}if (x < 0) x += originalMod;return x;
}/*** 拉格朗日插值法计算多项式在给定点的值* @param knownValues 已知的函数值点 (n=1到11对应的T(n)值)* @param x 需要计算的点 (对应 n-1)* @param mod 模数* @return 插值结果,即 T(n) mod 10007*/
int lagrangeInterpolation(const vector<int>& knownValues, int x, int mod) {int pointCount = knownValues.size(); // 已知点数量 (11个点)// 如果 x 在已知点范围内,直接返回对应的已知值if (x < pointCount) {return knownValues[x] % mod;}int result = 0;// 拉格朗日插值主循环for (int i = 0; i < pointCount; i++) {int numerator = 1; // 分子int denominator = 1; // 分母// 计算拉格朗日基函数for (int j = 0; j < pointCount; j++) {if (i == j) continue;// 计算分子: ∏(x - x_j)numerator = (long long)numerator * (x - j + mod) % mod;// 计算分母: ∏(x_i - x_j) denominator = (long long)denominator * (i - j + mod) % mod;}// 累加每一项: y_i * [∏(x - x_j)] / [∏(x_i - x_j)]result = (result + (long long)knownValues[i] * numerator % mod * modularInverse(denominator, mod)) % mod;}return result;
}int main() {// 已知的 n=1 到 11 的正确输出值(模 10007)// 这些值通过小规模暴力计算或题目已知结果得到vector<int> knownValues = {0, // n = 10, // n = 2 120, // n = 33200, // n = 45139, // n = 56959, // n = 63988, // n = 78968, // n = 84108, // n = 99728, // n = 10444 // n = 11};int inputN;while (cin >> inputN && inputN != 0) {if (inputN <= 11) {// 对于 n <= 11,直接输出已知值cout << knownValues[inputN - 1] << "\n";} else {// 对于 n > 11,使用拉格朗日插值计算// 注意:knownValues 的索引对应关系:索引 0 -> n=1, 索引 1 -> n=2, ..., 索引 10 -> n=11// 所以对于输入 n,对应的插值点 x = n - 1int interpolationPoint = inputN - 1;cout << lagrangeInterpolation(knownValues, interpolationPoint, MOD) << "\n";}}return 0;
}
总结
本题的解题关键在于发现 T(n)T(n)T(n) 是一个多项式函数,从而可以使用插值法在已知小规模数据点的情况下计算任意 nnn 对应的函数值。这种方法将原本不可行的 O(n5)O(n^5)O(n5) 计算转化为 O(1)O(1)O(1) 的查询,是解决此类大规模计算问题的典型思路。
技巧要点:
111. 识别问题的多项式性质
222. 选择合适的插值点数量
333. 正确处理模运算下的除法
444. 利用对称性简化问题分析
这种方法在竞赛中常用于处理看似复杂但实际具有规律性的大规模计算问题。