伯努利数求自然数幂和
伯努利数(指数生成函数)
如果我们用别的算法算出对于不同的\(t\)的自然数幂和的多项式,然后写在一张纸上,其实可以观察到一些性质(这里就不列出来了)。雅各布·伯努利就发现了其中的规律…
这里为了方便,我们修改一下我们的记号:\(S_t(n) = \sum_{i=0}^{n-1} i^t\)。我们把\(n^t\)的一项去掉了。这会比较方便接下来的分析,并且影响不大,就像我们在斯特林数那一节所做的一样。
伯努利的结果是:
其中\(B_i\)是伯努利数。这个式子的证明我们先放一放,先看看它有什么用。
注意到,当\(n=1\)时,\(S_t(n)\)就变成了\(0^t\),所以只有\(t=0\)时它才是\(1\),其它时候都是\(0\)。注意到在这个时候,我们得到了一个一侧含有伯努利数的式子:
而如果\(t=0\),我们可以得到\(B_0=1\)。现在我们就可以递推算\(B_t\)了,移项即可得到递推式,复杂度是\(O(t^2)\)的。
而且注意到上面伯努利得到的结论直接就是一个多项式的形式,所以通过伯努利数得到多项式是\(O(t)\)的,是这些方法里最快的了。
接下来我们需要证明这个东西…这个东西可以用归纳法证明,但是看起来非常复杂暴力…好在还有一种方法是指数生成函数。
一个数列\(f_n\)的指数生成函数是$\hat F(z) = \sum_{n \geq 0} f_n \frac{z^n}{n!} $。
所以如果我们把两个指数生成函数相乘,得到的指数生成函数是它们的二项卷积的指数生成函数。比如对于\(\hat F(z) \hat G(z) = \hat H(z)\):
我们可以对开始那个递推式使用指数生成函数:我们用\(n\)代替\(t+1\),然后在两侧加上\(B_n\)得到
左侧其实是\(\hat B(z)\)与一个系数全是\(1\)的数列的指数生成函数(\(e^z\))二项卷积,同时我们补上\(n=1\)的情况,此时在右边会多一个\(1\)。所以
变形就能得到\(\hat B(z) = \frac{z}{e^z-1}\),这就是伯努利数的指数生成函数。(这也得出了一种通过FFT对多项式求逆的一种\(O(t \log t)\)的预处理伯努利数的方法)
接下来考虑\(t\)次方和。我们尝试利用指数生成函数凑\(t\)次方和。因为普通生成函数下我们得到的是分母上的等差数列,只能写成调和数的形式,所以实际意义不大。而指数生成函数中我们有希望把等差数列挪到指数上,从而得到一个等比数列,然后就可以化简了。
这就是数列\(k^0,k^1,k^2,\cdots\)的指数生成函数,所以如果我们要求自然数幂和,我们只要把\(0\)到\(n-1\)的\(e^{kz}\)相加:
这就得到了一个简单的形式了。联系伯努利数的生成函数就有:
右侧又可以变成类似两个数列的二项卷积的形式,不过有一点不同,其中\(\frac{e^{nz}-1}{z}\)其实就是把\(n^0,n^1,n^2,\cdots\)的生成函数去掉常数项再在普通生成函数意义下挪了一位。这就导致了公式的二项式系数里面那个奇怪的\(+1\)。展开就是我们的公式了。
TJOI2018 教科书般的亵渎
小豆喜欢玩游戏,现在他在玩一个游戏遇到这样的场面,每个怪的血量为\(a_i\),且每个怪物血量均不相同,小豆手里有无限张“亵渎”。亵渎的效果是对所有的怪造成\(1\)点伤害,如果有怪死亡,则再次施放该法术。我们认为血量为\(0\)怪物死亡。
小豆使用一张 “亵渎”会获得一定的分数,分数计算如下,在使用一张“亵渎”之后,每一个被亵渎造成伤害的怪会产生\(x^k\),其中\(x\)是造成伤害前怪的血量为\(x\)和需要杀死所有怪物所需的“亵渎”的张数\(k\)。
输出小豆的最后可以获得的分数\(\mod 10^9+7\)。
【输入格式】
每组组测试数据第一行为\(n,m\),表示有当前怪物最高的血量\(n\),和\(m\)种没有出现的血量。
接下来\(m\)行,每行\(1\)个数\(a_i\),表示场上没有血量为\(a_i\)的怪物。
【数据范围】
对于\(100\%\)的数据,\(n\le 10^{13},m\le 50\)。
题解
每个没有出现的血量都会打断一次“亵渎”,所以需要杀死所有怪物所需的“亵渎”的张数\(k=m+1\)。
那么每次的贡献是一个自然数幂和-没有出现的数的幂和的形式,所以问题转化成了求自然数幂和。
用伯努利数解决,时间复杂度\(O(m^2)\)。
co int N=60;
LL n,a[N];
int m,b[N],c[N][N],inv[N];
int calc(LL n){
int ans=0;
for(int i=1;i<=m+1;++i)
ans=add(ans,mul(c[m+1][i],mul(b[m+1-i],fpow((n+1)%mod,i))));
ans=mul(ans,inv[m+1]);
return ans;
}
void real_main(){
read(n),read(m);
for(int i=1;i<=m;++i) read(a[i]);
a[++m]=++n;
sort(a+1,a+m+1);
int ans=0;
for(int i=1;i<=m;++i){
for(int j=i;j<=m;++j)
ans=add(ans,add(calc(a[j]-1),mod-calc(a[j-1])));
for(int j=i+1;j<=m;++j) a[j]-=a[i];
a[i]=0;
}
printf("%d\n",ans);
}
int main(){
inv[0]=inv[1]=1;
for(int i=2;i<=55;++i) inv[i]=mul(mod-mod/i,inv[mod%i]);
for(int i=0;i<=55;++i){
c[i][0]=c[i][i]=1;
for(int j=1;j<i;++j) c[i][j]=add(c[i-1][j-1],c[i-1][j]);
}
b[0]=1;
for(int i=1;i<=55;++i){
for(int j=0;j<i;++j) b[i]=add(b[i],mul(c[i+1][j],b[j]));
b[i]=mul(mod-b[i],inv[i+1]);
}
for(int t=read<int>();t--;) real_main();
return 0;
}