浅谈高次幂求和
须知
参考了 oi-wiki 的 this(里面也有许多本帖未提及的证明)插值部分参考了 this。
这是本帖用到的所有不常见的数学符号:
引入
我们都知道
即:
那么
其实可以用伯努利数计算得到。
假设我们已知零,一次的求和公式,现在要求二次求和公式。
移项:
将
全部相加:
即:
移项:
我们已知
即:
对于多次方和,可以使用相同的方法得到。
现在将它们排列在一起:
可以将系数取出来作为一个矩阵:
注意到(注意力惊人):
也就是说,第
最左边的数的我们可以发现,它为
所以现在要求
事实上,它就是伯努利数。
伯努利数
数列
可以发现
那它怎么求呢?
这里不加证明的给个公式:
再根据上面的推导,我们便可以求出
代码实现:
由于答案太大,通常加上取模,用逆元代替除法:
#include<bits/stdc++.h>
#define inv(x) power(x,mod-2)
using namespace std;
constexpr int mod=998244353;
int n;
vector<int>ans;
int vis[5005][5005];
int power(int x,int y){
int res=1;
while(y){
if(y&1) res=1ll*res*x%mod;
x=1ll*x*x%mod,y>>=1;
}
return res;
}
int C(int n,int m){
if(vis[n][m]) return vis[n][m];
return vis[n][m]=!m||n==m?1:(C(n-1,m-1)+C(n-1,m))%mod;
}
vector<int>get(int n){
vector<int>ans(n+1,0);
ans[0]=1;
for(int i=1;i<=n;i++){
if(i>1&&(i&1)) continue;
for(int j=0;j<i;j++) ans[i]=(1ll*ans[j]*C(i+1,j)+ans[i])%mod;
ans[i]=1ll*ans[i]*inv(i+1)%mod,ans[i]=-ans[i];
}
return ans;
}
int main(){
scanf("%d",&n),ans=get(n);
for(auto p:ans) printf("%d\n",p);
return 0;
}
求出伯努利数,我们便可以求出
#include<bits/stdc++.h>
#define inv(x) power(x,mod-2)
using namespace std;
constexpr int mod=998244353;
int k,n;
vector<int>ans;
int vis[5005][5005];
int power(int x,int y){
int res=1;
while(y){
if(y&1) res=1ll*res*x%mod;
x=1ll*x*x%mod,y>>=1;
}
return res;
}
int C(int n,int m){
if(vis[n][m]) return vis[n][m];
return vis[n][m]=!m||n==m?1:(C(n-1,m-1)+C(n-1,m))%mod;
}
vector<int>get(int n){
vector<int>ans(n+1,0);
ans[0]=1;
for(int i=1;i<=n;i++){
if(i>1&&(i&1)) continue;
for(int j=0;j<i;j++) ans[i]=(1ll*ans[j]*C(i+1,j)+ans[i])%mod;
ans[i]=1ll*ans[i]*inv(i+1)%mod,ans[i]=-ans[i];
}
return ans;
}
int sum(int k,int n){
vector<int>B=get(k);int ans=0;
for(int i=0;i<=k;i++) ans=((__int128)C(k+1,i)*B[i]*power(n+1,k-i+1)*inv(k+1)+ans)%mod;
return ans;
}
int main(){
scanf("%d%d",&k,&n);
printf("%d",sum(k,n));
return 0;
}
值得一提的是,这个做法的时间瓶颈在于求伯努利数,时间复杂度为
插值做法
我们都知道插值公式:
但直接插值显然是
在这里,横坐标显然都是连续整数,因此可以优化到
因为横坐标是连续整数,所以可以假设
先考虑分子:
这就可以预处理
而分母就将它拆成阶乘,使其不含有
于是我们就可以通过预处理阶乘和逆元来做到
全部代入就可以得到
现在我们回到问题。显然,
其实这个问题还可以组合数学相关知识直接推出答案式子,感兴趣的可以去最上面须知的 oi-wiki 链接查看。
代码实现:
#include<bits/stdc++.h>
using namespace std;
constexpr int mod=998244353;
int n,k,cnt,ans,flag[1000005],prime[1000005],f[1000005];
int pre[1000005],suf[1000005],fac[1000005],inv[1000005];
int power(int x,int y){
int res=1;
while(y){
if(y&1) res=1ll*res*x%mod;
x=1ll*x*x%mod,y>>=1;
}
return res;
}
void sieve(int x){
f[1]=1;
for(int i=2;i<=x;i++){
if(!flag[i]) prime[++cnt]=i,f[i]=power(i,k);
for(int j=1;j<=cnt;j++){
if(1ll*i*prime[j]>x) break;
flag[i*prime[j]]=true;
f[i*prime[j]] =1ll*f[i]*f[prime[j]]%mod;
if(!(i%prime[j])) break;
}
}
for(int i=2;i<=x;i++) f[i]=(f[i]+f[i-1])%mod;
}
int main() {
scanf("%d%d",&k,&n),sieve(k+2);
if(n<=k+2) return printf("%d",f[n]),0;
pre[0]=suf[k+3]=fac[0]=inv[0]=fac[1]=inv[1]=1;
for(int i=1;i<=k+2;i++) pre[i]=1ll*pre[i-1]*(n-i)%mod;
for(int i=k+2;i>=1;i--) suf[i]=1ll*suf[i+1]*(n-i)%mod;
for(int i=2;i<=k+2;i++){
fac[i]=1ll*fac[i-1]*i%mod;
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
}
for(int i=2;i<=k+2;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
for(int i=1;i<=k+2;i++){
int x=1ll*pre[i-1]*suf[i+1]%mod;
int y=1ll*inv[i-1]*inv[k-i+2]%mod;
int neg=((k+i)&1)?-1:1;
ans=(ans+1ll*(x*neg+mod)%mod*y%mod*f[i]%mod)%mod;
}
printf("%d",ans);
return 0;
}