伯努利数(指数生成函数)
如果我们用别的算法算出对于不同的(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\%)的数据,(nle 10^{13},mle 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
",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;
}