UVa 766 Sum of Powers

题目描述

一位学生想要计算如下形式的和:

Sk(n)=∑i=1nik S_k(n) = \sum_{i=1}^n i^k Sk(n)=i=1nik

其中 kkk 是一个固定的自然数,nnn 是变化的自然数。他发现,如果对每个 iii 计算 iki^kik 再求和,当 nnn 很大时计算量太大(需要 O(n)O(n)O(n) 次运算)。实际上,存在一种方法只需常数次运算(不随 nnn 变化)。

可以证明,Sk(n)S_k(n)Sk(n) 是一个关于 nnnk+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,找到这样一组系数,以便学生能快速计算。

输入格式

输入文件包含多组测试数据,每组数据包含一个整数 kkk0≤k≤200 \leq k \leq 200k20)。
第一行是数据集的数量,接着是一个空行。每组数据之间也有一个空行。

输出格式

对于每组数据,按顺序输出整数 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) 的有理系数表示形式。已知:

  1. Sk(n)S_k(n)Sk(n)nnnk+1k+1k+1 次多项式
  2. 可以表示为 Sk(n)=1M∑j=0k+1ajnjS_k(n) = \frac{1}{M} \sum_{j=0}^{k+1} a_j n^jSk(n)=M1j=0k+1ajnj
  3. 要求 MMM 为正整数且最小
  4. 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=0k(1)j(jk+1)Bjnk+1j=k+11j=0k(jk+1)Bj+nk+1j

Bj+=(−1)jBj B_j^+ = (-1)^j B_j Bj+=(1)jBj

其中 BjB_jBj 是伯努利数。

伯努利数定义

伯努利数 BjB_jBj 可以通过以下递推关系定义:

  • B0=1B_0 = 1B0=1
  • 对于 m≥1m \geq 1m1∑j=0m(m+1j)Bj=0\sum_{j=0}^{m} \binom{m+1}{j} B_j = 0j=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)

是一致的。

解题思路

方法选择

我们选择使用伯努利数方法,因为:

  1. 效率高:时间复杂度为 O(k2)O(k^2)O(k2),对于 k≤20k \leq 20k20 完全足够
  2. 数值稳定:使用分数运算避免浮点数精度问题
  3. 实现简单:代码相对简洁
算法步骤
  1. 预处理组合数:使用杨辉三角计算 (nk)\binom{n}{k}(kn),存储在数组中
  2. 计算伯努利数 Bj+B_j^+Bj+
    • 使用递推公式:∑j=0m(m+1j)Bj=0\sum_{j=0}^{m} \binom{m+1}{j} B_j = 0j=0m(jm+1)Bj=0
    • 设置 B0=1B_0 = 1B0=1B1=12B_1 = \frac{1}{2}B1=21
    • 使用分数运算(分子/分母对)
  3. 计算多项式系数
    • 对于 j=0,1,…,kj = 0, 1, \ldots, kj=0,1,,kak+1−j=(k+1j)Bja_{k+1-j} = \binom{k+1}{j} B_jak+1j=(jk+1)Bj
    • 所有系数乘以 k+1k+1k+1:得到 a~j=(k+1)⋅aj\tilde{a}_j = (k+1) \cdot a_ja~j=(k+1)aj
  4. 通分
    • 找到所有 a~j\tilde{a}_ja~j 分母的最小公倍数 MMM
    • 将所有系数乘以 MMM 得到整数系数
  5. 约分
    • 找到所有系数和 MMM 的最大公约数
    • 进行约分,确保 MMM 最小
  6. 符号处理
    • 确保最高次系数 ak+1>0a_{k+1} > 0ak+1>0
    • 确保 M>0M > 0M>0
  7. 处理特殊情况
    • k=0k = 0k=0 时:S0(n)=n=1⋅n+0S_0(n) = n = 1 \cdot n + 0S0(n)=n=1n+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 20k20 非常高效
空间复杂度
  • 存储组合数:O(k2)O(k^2)O(k2)
  • 存储伯努利数和系数:O(k)O(k)O(k)
  • 总空间复杂度:O(k2)O(k^2)O(k2)

关键点

  1. 伯努利数的符号约定:必须使用 B1+=12B_1^+ = \frac{1}{2}B1+=21 才能得到与题目示例一致的输出
  2. 分数运算:使用分数表示避免精度问题,每次运算后立即约分
  3. 边界情况k=0k = 0k=0 需要特殊处理
  4. 输出格式:注意组间空行

代码实现

// 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 20k20 的所有情况。

需要注意的细节包括:

  1. 处理 k=0k = 0k=0 的特殊情况
  2. 使用正确的伯努利数约定(B1+=12B_1^+ = \frac{1}{2}B1+=21
  3. 确保输出格式正确(组间空行)
  4. 进行通分和约分,使 MMM 最小

这个解决方案展示了如何将数学理论转化为高效算法,是数学与计算机科学结合的典型例子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值