寒假集训比赛时遇到了一道《WYF的盒子》,题目大意就是求 ∑ni=mik 对 p 取模的结果。
其中有的数据点满足 n−m≤5000 但 k≤1012 ,剩下的数据点满足 n,m≤1012 且 k≤2000 。对于所有数据模数 p≤1012 。
其中 n 和 m 相差很小的点显然可以使用快速幂,但是剩下的点怎么做?换言之,我们需要找到一个时间复杂度之和 k 有关的高效的求自然数幂和的算法。
运用二项式定理我们可以推出一般的公式。
暑假时HeD大神给我们讲了这两种解法,初中党表示一脸懵逼。
拉格朗日插值法其实不难,拿高中选修数论初步看看就懂了。但是还是避免不了除法。
牛顿插值法只需要差分,不需要做除,时间复杂度 O(k2) ,是很优秀的算法,然而我不会。
这个我也不会,但是我有链接:http://blog.csdn.net/acdreamers/article/details/38929067
然而其实它也要做除法。
听说可以,然而我并不能YY出来。
BB了这么多,终于到重点了。
第一类斯特林数是什么鬼呢?它有一个圆环排列的定义,但是我并不知道。
于是我便引用它最初发现时的定义。
记
下面开始推算
#include
#include
#include
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;j1][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;jsum[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;
}