首页 > 代码库 > 伯努利数与自然数幂和
伯努利数与自然数幂和
今天我们讨论的问题是如何有效地求自然数的幂和。接下来以3个经典题目为例来讲解。
题目:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1864
分析:其实求自然数的幂和方法有很多种,先来看看普通的递推求法,由于
那么对于所有的累加得到
进一步得到
可以看出这是一个递推式,如果我们记
那么得到如下递归式
递归出口是
为了提高效率,在递归的时候需要记忆化。由于要用到大数,用Java实现。
代码:
import java.math.*; import java.util.*; public class Main { public static final int N = 105; public static final BigInteger FLAG = (BigInteger.ZERO).subtract(BigInteger.ONE); public static BigInteger[][] C = new BigInteger[N][N]; public static BigInteger[] ans = new BigInteger[N]; public static void Init(){ for(int i=0; i<N; i++){ C[i][0] = C[i][i] = BigInteger.ONE; if(i == 0) continue; for(int j=1; j<i; j++) C[i][j] = C[i-1][j].add(C[i-1][j-1]); } } public static BigInteger Solve(BigInteger n, int k){ if(ans[k].compareTo(FLAG) != 0){ return ans[k]; } if(k == 1){ ans[k] = ((n.add(BigInteger.ONE)).multiply(n)).divide(BigInteger.valueOf(2)); return ans[k]; } BigInteger tmp = BigInteger.ONE; for(int i=0; i<k+1; i++){ tmp = tmp.multiply(n.add(BigInteger.ONE)); } tmp = tmp.subtract(n.add(BigInteger.ONE)); BigInteger sum = BigInteger.ZERO; for(int i=1; i<k; i++){ BigInteger t = C[k+1][i+1].multiply(Solve(n, k-i)); sum = sum.add(t); } ans[k] = (tmp.subtract(sum)).divide(BigInteger.valueOf(k+1)); return ans[k]; } public static void main(String[] args){ Init(); Scanner cin = new Scanner(System.in); while(cin.hasNext()){ BigInteger n = cin.nextBigInteger(); int k = cin.nextInt(); for(int i=0; i<N; i++){ ans[i] = FLAG; } System.out.println(Solve(n, k)); } } }
题目:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1228
分析:本题题意就是求自然数的幂和,但是它的case比较多。对于求幂和本身就需要的时间复杂度,如果继
续用上述方法来求自然数的幂和,5000个case会TLE,接下来介绍另一个求自然数幂和的方法,它是基于伯
努利数的,公式描述如下
可以看出只要我们预处理出每一项,就可以在线性时间内求得自然数的幂和。前面的倒数可以用递推法求逆元
预处理,组合数也可以预处理,也可以先预处理,现在关键是如何预处理伯努利数。
伯努利数满足条件,且有
那么继续得到
这就是伯努利数的递推式,逆元部分同样可以预处理。
代码:
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; typedef long long LL; const LL MOD = 1000000007; const int N = 2005; LL C[N][N]; LL B[N],Inv[N]; LL Tmp[N]; LL n; void Init() { //预处理组合数 for(int i=0; i<N; i++) { C[i][0] = C[i][i] = 1; if(i == 0) continue; for(int j=1; j<i; j++) C[i][j] = (C[i-1][j] % MOD + C[i-1][j-1] % MOD) % MOD; } //预处理逆元 Inv[1] = 1; for(int i=2; i<N; i++) Inv[i] = (MOD - MOD / i) * Inv[MOD % i] % MOD; //预处理伯努利数 B[0] = 1; for(int i=1; i<N; i++) { LL ans = 0; if(i == N - 1) break; for(int j=0; j<i; j++) { ans += C[i+1][j] * B[j]; ans %= MOD; } ans *= -Inv[i+1]; ans = (ans % MOD + MOD) % MOD; B[i] = ans; } } LL Work(int k) { LL ans = Inv[k+1]; LL sum = 0; for(int i=1; i<=k+1; i++) { sum += C[k+1][i] * Tmp[i] % MOD * B[k+1-i] % MOD; sum %= MOD; } ans *= sum; ans %= MOD; return ans; } int main() { int T; Init(); scanf("%d", &T); while(T--) { int k; scanf("%I64d %d", &n, &k); n %= MOD; Tmp[0] = 1; for(int i=1; i<N; i++) Tmp[i] = Tmp[i-1] * (n + 1) % MOD; printf("%I64d\n", Work(k)); } return 0; }
题目:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1258
分析:本题与上题不同的是值比较大,达到50000,如果采用同样的方法,会TLE的。那么必定要进行优化。
对上述的表达式继续进行化简,得到
可以看出后面的那一坨是卷积的形式,那么可以用FFT来做。
伯努利数与自然数幂和