前言
寒假集训比赛时遇到了一道《WYF的盒子》,题目大意就是求
∑ni=mik
对
p
取模的结果。
其中有的数据点满足
其中
n
和
普通公式
运用二项式定理我们可以推出一般的公式。
推算过程链接:http://wenku.baidu.com/link?url=F07Cj1SxmyNr8_EriB3UrJU8tn4f3aByvpYKPvrLCoqV2HWjeXG1y6XWbgFQL8mBcP5Z0is1Sqpe0zxbDRmMSsBjt-0mQRRvvHTMqq57gpK
当然我们也可以不使用二项式定理,只研究递推关系,也可以得到相同的公式。
推算过程链接:http://blog.csdn.net/acdreamers/article/details/38929067
这种公式固然优美,时间复杂度为 O(k2) ,但是不可避免地要做除法,如果除数和模数互质那好办,直接逆元即可。但是这里显然不满足。
拉格朗日插值法&牛顿插值法
暑假时HeD大神给我们讲了这两种解法,初中党表示一脸懵逼。
拉格朗日插值法其实不难,拿高中选修数论初步看看就懂了。但是还是避免不了除法。
牛顿插值法只需要差分,不需要做除,时间复杂度
O(k2)
,是很优秀的算法,然而我不会。
伯努利数
这个我也不会,但是我有链接:http://blog.csdn.net/acdreamers/article/details/38929067
然而其实它也要做除法。
矩阵乘法
听说可以,然而我并不能YY出来。
第一类斯特林数
BB了这么多,终于到重点了。
第一类斯特林数是什么鬼呢?它有一个圆环排列的定义,但是我并不知道。
于是我便引用它最初发现时的定义。
记
即 f(n,m)=n!(n−m)!=n(n−1)(n−2)...(n−m+1)
则第一类斯特林数就表示 n 的各次幂的系数。
其实第一类斯特林数分两种:有符号
即
可以发现 ss(n,m)=(−1)n+msu(n,m) 。不过我们下面不需要使用 su 。
至于 ss 和 su 的递推公式,就由大家自己推算了。利用第一类斯特林数的定义很容易推出来,实在不会就参考我的代码。
第一类斯特林数的递推复杂度是 O(k2) 的。
再讲一个显然的东西。
这个拆开来约下分就知道了。
还有一个(并不)显然的东西。
这个使用组合数的杨辉三角递推式拆开就知道了。
下面开始推算
显然 ss(k,k)=1 ,于是我们移项之后可以得到
对 j 求和,记
这里面的 f 可以
至此问题完美解决。
代码实现
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long LL;
const int K=2050;
LL ss[K][K],sum[K];
LL k,n,m,p,ans;
const int A=1000000;
LL mult(LL x,LL y)
{
LL a1=x/A,a2=x%A,b1=y/A,b2=y%A;
LL ret=a1*b1%p*A%p*A%p;
ret+=a1*b2%p*A%p;
ret+=a2*b1%p*A%p;
ret+=a2*b2%p;
return ret%p;
}
LL quick_power(LL x,LL y)
{
LL ret=1;
while (y)
{
if (y&1)
ret=mult(ret,x);
x=mult(x,x);
y>>=1;
}
return ret;
}
void pre()
{
ss[0][0]=1;
for (int i=1;i<=k;i++)
ss[i][0]=0,ss[i][i]=1;
for (int i=1;i<=k;i++)
for (int j=1;j<i;j++)
ss[i][j]=ss[i-1][j-1]+mult(-i+1,ss[i-1][j]);
}
LL calc(LL n)
{
if (n&1)
sum[1]=mult(n,(n+1)/2);
else
sum[1]=mult(n/2,n+1);
for (int i=2;i<=k;i++)
{
sum[i]=1;
for (LL j=n+1;j>=n-i+1;j--)
if (j%(i+1))
sum[i]=mult(sum[i],j);
else
sum[i]=mult(sum[i],j/(i+1));
for (int j=0;j<i;j++)
sum[i]=(sum[i]-mult(ss[i][j],sum[j])+p)%p;
}
return sum[k];
}
int main()
{
freopen("box.in","r",stdin);
freopen("box.out","w",stdout);
scanf("%lld%lld%lld%lld",&k,&n,&m,&p);
if (n-m<=5000)
for (LL i=m;i<=n;i++)
(ans+=quick_power(i,k))%=p;
else
{
pre();
ans=(calc(n)-calc(m-1)+p)%p;
}
printf("%lld\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}

本文介绍了如何高效地求解自然数k次幂和的问题,探讨了快速幂、拉格朗日插值法、牛顿插值法、伯努利数和矩阵乘法等方法,重点讲解了利用第一类斯特林数进行计算,并给出了代码实现。
507

被折叠的 条评论
为什么被折叠?



