题目描述
一位学生想要计算如下形式的和:
Sk(n)=∑i=1nik S_k(n) = \sum_{i=1}^n i^k Sk(n)=i=1∑nik
其中 kkk 是一个固定的自然数,nnn 是变化的自然数。他发现,如果对每个 iii 计算 iki^kik 再求和,当 nnn 很大时计算量太大(需要 O(n)O(n)O(n) 次运算)。实际上,存在一种方法只需常数次运算(不随 nnn 变化)。
可以证明,Sk(n)S_k(n)Sk(n) 是一个关于 nnn 的 k+1k+1k+1 次多项式,其系数为有理数,即:
Sk(n)=1M(ak+1nk+1+aknk+…+a1n+a0) S_k(n) = \frac{1}{M} \left( a_{k+1} n^{k+1} + a_k n^k + \ldots + a_1 n + a_0 \right) Sk(n)=M1(ak+1nk+1+aknk+…+a1n+a0)
其中 M,ak+1,ak,…,a1,a0M, a_{k+1}, a_k, \ldots, a_1, a_0M,ak+1,ak,…,a1,a0 都是整数。
我们要求 MMM 为正整数且尽可能小。在此条件下,这些整数是唯一确定的。你需要编写一个程序,对于给定的 kkk,找到这样一组系数,以便学生能快速计算。
输入格式
输入文件包含多组测试数据,每组数据包含一个整数 kkk(0≤k≤200 \leq k \leq 200≤k≤20)。
第一行是数据集的数量,接着是一个空行。每组数据之间也有一个空行。
输出格式
对于每组数据,按顺序输出整数 M,ak+1,ak,…,a1,a0M, a_{k+1}, a_k, \ldots, a_1, a_0M,ak+1,ak,…,a1,a0,数字之间用一个空格隔开。确保 MMM 为正且尽可能小。
每组输出之间用一个空行隔开。
示例输入
1
2
示例输出
6 2 3 1 0
题目分析
问题本质
我们需要找到多项式 Sk(n)S_k(n)Sk(n) 的有理系数表示形式。已知:
- Sk(n)S_k(n)Sk(n) 是 nnn 的 k+1k+1k+1 次多项式
- 可以表示为 Sk(n)=1M∑j=0k+1ajnjS_k(n) = \frac{1}{M} \sum_{j=0}^{k+1} a_j n^jSk(n)=M1∑j=0k+1ajnj
- 要求 MMM 为正整数且最小
- aja_jaj 为整数,且 a0=0a_0 = 0a0=0(因为 Sk(0)=0S_k(0) = 0Sk(0)=0)
数学背景
这个问题涉及到幂和公式(Faulhaber’s formula\texttt{Faulhaber's formula}Faulhaber’s formula)和伯努利数(Bernoulli numbers\texttt{Bernoulli numbers}Bernoulli numbers)。具体公式为:
Sk(n)=1k+1∑j=0k(−1)j(k+1j)Bjnk+1−j=1k+1∑j=0k(k+1j)Bj+nk+1−j S_k(n) = \frac{1}{k+1} \sum_{j=0}^{k}(-1)^j \binom{k+1}{j} B_j n^{k+1-j} = \frac{1}{k+1} \sum_{j=0}^{k}\binom{k+1}{j} B_j^+ n^{k+1-j} Sk(n)=k+11j=0∑k(−1)j(jk+1)Bjnk+1−j=k+11j=0∑k(jk+1)Bj+nk+1−j
Bj+=(−1)jBj B_j^+ = (-1)^j B_j Bj+=(−1)jBj
其中 BjB_jBj 是伯努利数。
伯努利数定义
伯努利数 BjB_jBj 可以通过以下递推关系定义:
- B0=1B_0 = 1B0=1
- 对于 m≥1m \geq 1m≥1:∑j=0m(m+1j)Bj=0\sum_{j=0}^{m} \binom{m+1}{j} B_j = 0∑j=0m(jm+1)Bj=0
由此可得:
- B1=−12B_1 = -\frac{1}{2}B1=−21
- B2=16B_2 = \frac{1}{6}B2=61
- B3=0B_3 = 0B3=0
- B4=−130B_4 = -\frac{1}{30}B4=−301
- …
但是,题目示例显示对于 k=2k=2k=2,输出是 6 2 3 1 06\ 2\ 3\ 1\ 06 2 3 1 0,对应多项式:
S2(n)=16(2n3+3n2+n) S_2(n) = \frac{1}{6}(2n^3 + 3n^2 + n) S2(n)=61(2n3+3n2+n)
标准公式给出的是:
S2(n)=13n3+12n2+16n=16(2n3+3n2+n) S_2(n) = \frac{1}{3}n^3 + \frac{1}{2}n^2 + \frac{1}{6}n = \frac{1}{6}(2n^3 + 3n^2 + n) S2(n)=31n3+21n2+61n=61(2n3+3n2+n)
是一致的。
解题思路
方法选择
我们选择使用伯努利数方法,因为:
- 效率高:时间复杂度为 O(k2)O(k^2)O(k2),对于 k≤20k \leq 20k≤20 完全足够
- 数值稳定:使用分数运算避免浮点数精度问题
- 实现简单:代码相对简洁
算法步骤
- 预处理组合数:使用杨辉三角计算 (nk)\binom{n}{k}(kn),存储在数组中
- 计算伯努利数 Bj+B_j^+Bj+:
- 使用递推公式:∑j=0m(m+1j)Bj=0\sum_{j=0}^{m} \binom{m+1}{j} B_j = 0∑j=0m(jm+1)Bj=0
- 设置 B0=1B_0 = 1B0=1,B1=12B_1 = \frac{1}{2}B1=21
- 使用分数运算(分子/分母对)
- 计算多项式系数:
- 对于 j=0,1,…,kj = 0, 1, \ldots, kj=0,1,…,k:ak+1−j=(k+1j)Bja_{k+1-j} = \binom{k+1}{j} B_jak+1−j=(jk+1)Bj
- 所有系数乘以 k+1k+1k+1:得到 a~j=(k+1)⋅aj\tilde{a}_j = (k+1) \cdot a_ja~j=(k+1)⋅aj
- 通分:
- 找到所有 a~j\tilde{a}_ja~j 分母的最小公倍数 MMM
- 将所有系数乘以 MMM 得到整数系数
- 约分:
- 找到所有系数和 MMM 的最大公约数
- 进行约分,确保 MMM 最小
- 符号处理:
- 确保最高次系数 ak+1>0a_{k+1} > 0ak+1>0
- 确保 M>0M > 0M>0
- 处理特殊情况:
- k=0k = 0k=0 时:S0(n)=n=1⋅n+0S_0(n) = n = 1 \cdot n + 0S0(n)=n=1⋅n+0,直接输出 1 1 01\ 1\ 01 1 0
时间复杂度
- 计算组合数:O(k2)O(k^2)O(k2)
- 计算伯努利数:O(k2)O(k^2)O(k2)
- 总时间复杂度:O(k2)O(k^2)O(k2),对于 k≤20k \leq 20k≤20 非常高效
空间复杂度
- 存储组合数:O(k2)O(k^2)O(k2)
- 存储伯努利数和系数:O(k)O(k)O(k)
- 总空间复杂度:O(k2)O(k^2)O(k2)
关键点
- 伯努利数的符号约定:必须使用 B1+=12B_1^+ = \frac{1}{2}B1+=21 才能得到与题目示例一致的输出
- 分数运算:使用分数表示避免精度问题,每次运算后立即约分
- 边界情况:k=0k = 0k=0 需要特殊处理
- 输出格式:注意组间空行
代码实现
// Sum of Powers
// UVa ID: 766
// Verdict: Accepted
// Submission Date: 2025-12-01
// UVa Run Time: 0.010s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXK = 21;
long long comb[MAXK + 2][MAXK + 2];
void initComb() {
for (int i = 0; i <= MAXK + 1; ++i) {
comb[i][0] = comb[i][i] = 1;
for (int j = 1; j < i; ++j)
comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j];
}
}
// 使用递推计算伯努利数 B_j^+
vector<pair<long long, long long>> bernoulliPlus(int k) {
vector<pair<long long, long long>> B(k + 1); // 存储分数 num/den
B[0] = {1, 1}; // B0 = 1/1
for (int m = 1; m <= k; ++m) {
// 计算 sum_{j=0}^{m-1} C(m+1, j) * B_j
pair<long long, long long> sum = {0, 1};
for (int j = 0; j < m; ++j) {
// 分数加法:a/b + c/d = (ad + bc)/(bd)
long long a = comb[m + 1][j] * B[j].first;
long long b = B[j].second;
long long newDen = sum.second / __gcd(sum.second, b) * b;
long long newNum = sum.first * (newDen / sum.second) + a * (newDen / b);
long long g = __gcd(abs(newNum), newDen);
sum = {newNum / g, newDen / g};
}
// B_m = -sum / (m+1)
long long num = -sum.first;
long long den = sum.second * (m + 1);
long long g = __gcd(abs(num), den);
B[m] = {num / g, den / g};
}
// 修正 B1^+ = +1/2(而不是 -1/2)
B[1] = {1, 2};
return B;
}
void solve(int k, vector<long long>& coeff, long long& M) {
vector<pair<long long, long long>> B = bernoulliPlus(k);
// 计算系数
vector<pair<long long, long long>> polyCoeff(k + 2, {0, 1});
for (int j = 0; j <= k; ++j) {
long long binomial = comb[k + 1][j];
// 分数乘法:(a/b) * c = (a*c)/b
long long num = binomial * B[j].first;
long long den = B[j].second;
long long g = __gcd(abs(num), den);
num /= g;
den /= g;
// 存储到对应位置
polyCoeff[k + 1 - j] = {num, den};
}
// 所有系数乘以 (k+1) 得到 a_j/(k+1) 形式
for (int j = 0; j <= k + 1; ++j) {
polyCoeff[j].second *= (k + 1);
long long g = __gcd(abs(polyCoeff[j].first), polyCoeff[j].second);
if (g != 0) {
polyCoeff[j].first /= g;
polyCoeff[j].second /= g;
}
}
// 找到所有分母的最小公倍数
M = 1;
for (int j = 0; j <= k + 1; ++j) {
if (polyCoeff[j].first != 0) {
M = M / __gcd(M, polyCoeff[j].second) * polyCoeff[j].second;
}
}
// 转换为整数系数
coeff.resize(k + 2);
for (int j = 0; j <= k + 1; ++j) {
coeff[j] = polyCoeff[j].first * (M / polyCoeff[j].second);
}
// 约分
long long g = 0;
for (int j = 0; j <= k + 1; ++j) {
g = __gcd(g, abs(coeff[j]));
}
g = __gcd(g, M);
if (g > 1) {
M /= g;
for (int j = 0; j <= k + 1; ++j) {
coeff[j] /= g;
}
}
// 确保最高次系数为正
if (coeff[k + 1] < 0) {
M = -M;
for (int j = 0; j <= k + 1; ++j) {
coeff[j] = -coeff[j];
}
}
// 确保 M 为正
if (M < 0) {
M = -M;
for (int j = 0; j <= k + 1; ++j) {
coeff[j] = -coeff[j];
}
}
}
int main() {
initComb();
int t;
cin >> t;
for (int tc = 0; tc < t; ++tc) {
if (tc > 0) cout << endl;
int k;
cin >> k;
if (k == 0) {
cout << "1 1 0\n";
continue;
}
vector<long long> coeff;
long long M;
solve(k, coeff, M);
cout << M;
for (int j = k + 1; j >= 0; --j) {
cout << " " << coeff[j];
}
cout << endl;
}
return 0;
}
总结
本题的关键在于理解幂和公式与伯努利数的关系,并注意伯努利数的符号约定。通过使用分数运算和递推计算伯努利数,我们可以高效地求解多项式系数。算法的时间复杂度和空间复杂度都在可接受范围内,能够正确处理 k≤20k \leq 20k≤20 的所有情况。
需要注意的细节包括:
- 处理 k=0k = 0k=0 的特殊情况
- 使用正确的伯努利数约定(B1+=12B_1^+ = \frac{1}{2}B1+=21)
- 确保输出格式正确(组间空行)
- 进行通分和约分,使 MMM 最小
这个解决方案展示了如何将数学理论转化为高效算法,是数学与计算机科学结合的典型例子。
1596

被折叠的 条评论
为什么被折叠?



