题意:求 ∑ i = 0 n ∑ j = 1 a + d ∗ i ∑ x = 1 j x k \displaystyle\sum_{i = 0}^n\sum_{j = 1}^{a +d*i}\sum_{x = 1}^jx^k i=0∑nj=1∑a+d∗ix=1∑jxk
根据过去的经验,可以得知
∑
i
=
1
n
x
k
\displaystyle\sum_{i = 1}^nx^k
i=1∑nxk 是一个以
n
n
n 为自变量的
k
+
1
k + 1
k+1 次多项式,可以得到推广结论:
k
k
k 次多项式之和得到的是
k
+
1
k + 1
k+1 次多项式。
那么就可以得知题意所求式子是一个 k + 3 k + 3 k+3 次多项式,可以通过拉格朗日插值来求解。
从内层求和到外层求和,依次进行插值,求出 m x mx mx 个点的点值( m x ≥ k + 1 mx \geq k + 1 mx≥k+1),对于中间某一层求和进行插值时,其 y y y 的计算依赖于上一层插值得到的多项式。由于 x x x 由自己选择可以连续,因此最高在 k 2 k^2 k2 时间内完成一层求和的插值。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
const int mod = 1234567891;
typedef long long ll;
int mx;
inline ll add(ll x, ll y) {
x += y;
if (x >= mod) x -= mod;
return x;
}
inline ll sub(ll x, ll y) {
x -= y;
if (x < 0) x += mod;
return x;
}
inline ll mul(ll x, ll y) {
return x * y % mod;
}
ll fpow(ll a,ll b) {
ll r = 1;
while(b) {
if (b & 1) r = mul(r,a);
b >>= 1;
a = mul(a,a);
}
return r;
}
ll g[maxn],f[maxn],k,a,n,d,sz;
ll fac[maxn],ifac[maxn];
ll cal(ll g[maxn],ll x) { //拉格朗日插值计算多项式
if (x <= mx) return g[x];
ll tmp = 1,inv,ans = 0;
for (int i = 1; i <= mx; i++)
tmp = mul(tmp,x - i);
for (int i = 1; i <= mx; i++) {
ll res = 1, inv = fpow(x - i,mod - 2);
res = mul(res,g[i]);
res = mul(res,ifac[i - 1]);
res = mul(res,ifac[mx - i]);
res = mul(res,inv);
res = mul(res,tmp);
if ((mx - i) & 1) res = mul(res,-1);
if (res < 0) res += mod;
ans = add(ans,res);
}
return ans;
}
int main() {
fac[0] = 1;
for (int i = 1; i <= 1000; i++)
fac[i] = mul(fac[i - 1],i);
ifac[1000] = fpow(fac[1000],mod - 2);
for (int i = 1000 - 1; i >= 0; i--)
ifac[i] = mul(ifac[i + 1],i + 1);
scanf("%d",&sz);
while (sz--) {
scanf("%lld%lld%lld%lld",&k,&a,&n,&d);
mx = k + 5;
g[0] = 0;
for (int i = 1; i <= mx; i++) {
g[i] = g[i - 1];
for (int j = 1; j <= i; j++)
g[i] = add(g[i],fpow(j,k));
}
f[0] = cal(g,a);
for (int i = 1; i <= mx; i++) {
f[i] = f[i - 1];
ll t = 1ll * a + d * i;
if (t <= mx) f[i] = add(f[i],g[t]);
else f[i] = add(f[i],cal(g,t % mod));
}
printf("%lld\n",cal(f,n));
}
return 0;
}