洛谷P5907 数列求和加强版 / SPOJ MOON4

题面描述

i=1nikai

998244353 取模的结果。

O(k)做法

为了推导方便,下令 p=a1。即求

i=1nikpi

我们裂项,即尝试寻找多项式 f(x),使得

xkpx=f(x)px1f(x+1)px

xk=pf(x)f(x+1)()

答案将是 f(1)f(n+1)pn

显然 degf=k

这样临项点值的差,由于恒等式 (x+1)n_xn_=nxn1_,可以考虑展开成下降幂来推导。具体地,设 f(x)=i=0ncixi_,则可以展开 () 式:

xk=i=0k{ki}xi_=i=0kpcixi_i=0kci(x+1)i_=i=0k(p1)cixi_i=1kicixi1_=(p1)ckxk_+i=0k1((p1)ci(i+1)ci+1)xi_

对比系数能够得到:

(p1)ck={kk}=1

i=0,1...,k1

(p1)ci(i+1)ci+1={ki}

求一行斯特林数是 O(klogk)的,需要进一步优化。

直接求每一项系数也许有些困难,但是这里有一个取巧的办法:
如果我们知道 f(0),那么通过()式,可以递推出 f(1),...f(k),那么就可以利用这 k+1 个点值进行线性拉格朗日插值,避开了这个难处。

f(0)=c0,因此下面我们只需要求出 c0 即可。省略这一步过程,我们恰好可以将这个系数的递推式求出来,即

c0=1p1i=0k{ki}i!(p1)i

这个式子是可以 O(k) 求解的,参见 EI 的 Binomial Sum。由于我也是第一次学这个所以下面还是写一下过程。

下面令 t=(p1)1 ,求

i=0k{ki}tii!

写成生成函数就是

i=0kti{ki}i!=i=0kti[zkk!](expz1)i=[zkk!]i=0k(t(expz1))i=[zkk!]11t(expz1)

u=expz,对于 z 来说,这本应是一个无穷级数,但利用 expz1 没有常数项的性质,我们可以将对于 u 的求和上限限制在 k 处(截断),之后的项都不会有贡献。因此就是求

[zkk!]1(t(expz1))k+11t(expz1)

那么 GF 写作:

G(u)=1(t(u1))k+11t(u1)

这是一个关于 u 的有理分式,将分母移到一边就可以 O(k) 求出 ui 处系数,然后就做完了。退役OIer不想省略下面的细节,继续推导。我们设 G(u)=i0giui,那么

g0=G(0)=1+tk+1(1)k1+t

(1+ttu)G=1tk+1(u1)k+1

提取系数就是

(1+t)gitgi1=tk+1(k+1i)(1)ki

最后,我们得到了,

c0=t[zkk!]i=0kgiexpiz=ti=0kgiik

这道题几乎做完了。剩下的就是特判 a=p=1 的情况,此时问题退化为自然数幂和,可以直接线性拉格朗日插值。

代码(代码中,使用 m 代替了 k):

#include <cstdio>
#define ll long long
const int M = 1e7, mod = 998244353;
inline int mul(int a, int b) {return (ll)a * b % mod;}
inline int add(int a, int b) {int ret = a + b; return ret >= mod ? ret - mod : ret;}
inline int minus(int a, int b) {int ret = a - b; return ret < 0 ? ret + mod : ret;}
inline int qpow(int a, int b) {
    int ret = 1;
    for(; b; b >>= 1, a = mul(a, a))
        if(b & 1)   ret = mul(ret, a);
    return ret;
}
int f[M + 5], g[M + 5], p, n, m, ip, ip1;
int fac[M + 5], invf[M + 5];
int pre, suf[M + 5];
int cnt, v[M + 5], pr[M >> 2], pwm[M + 5];
inline int C(int m, int n) {return (ll)fac[m] * invf[n] % mod * invf[m - n] % mod;}
inline int pn1(int num, int p) {return p & 1 ? mod - num : num;}

int main() {
    scanf("%d%d%d", &n, &ip, &m);
    //initialization
    fac[0] = 1;
    p = qpow(ip, mod - 2);
    for(int i = 1; i <= m + 1; ++i) fac[i] = mul(fac[i - 1], i);
    invf[m + 1] = qpow(fac[m + 1], mod - 2);
    for(int i = m; i >= 0; --i) invf[i] = mul(invf[i + 1], i + 1);

    pwm[1] = 1;
    for(int i = 2; i <= m + 2; ++i) {
        if(!v[i]) v[i] = pr[++cnt] = i, pwm[i] = qpow(i, m);
        for(int j = 1; j <= cnt; ++j) {
            if(pr[j] > v[i] || i * pr[j] > m + 2)    break;
            v[i * pr[j]] = pr[j];
            pwm[i * pr[j]] = mul(pwm[i], pwm[pr[j]]);
        }
    }
    ip1 = qpow(p - 1, mod - 2);
    if(p ^ 1) {
	    //calulate g, f
	    int t1 = qpow(ip1 + 1, mod - 2), t2 = qpow(ip1, m + 1);
	    // t1 = 1/t + 1, t2 = t ^ {m + 1}
	    g[0] = mul(t1, 1 + pn1(t2, m));
	    for(int i = 1; i <= m; ++i) {
	        g[i] = mul(t1, mul(ip1, g[i - 1]) + mul(C(m + 1, i), pn1(t2, m - i)));
	        f[0] = add(f[0], mul(pwm[i], g[i]));
	    }
		f[0] = mul(ip1, f[0]);
	}
    if(p ^ 1)	for(int i = 1; i <= m; ++i)
        	f[i] = minus(mul(p, f[i - 1]), pwm[i - 1]);
	else for(int i = 1; i <= m + 1; ++i)
		f[i] = add(f[i - 1], pwm[i]);
       
    //prepare for lagruange interpolation
    if(p == 1)	--n, ++m;
    pre = n + 1, suf[m + 1] = 1;
//    for(int i = 1; i <= m; ++i) pre[i] = mul(pre[i - 1], (mod + n + 1 - i) % mod);
    for(int i = m; i >= 1; --i) suf[i] = mul(suf[i + 1], minus(n + 1, i));
    
    //calculate answer
    int Ans = mul(f[0], pn1(mul(suf[1], invf[m]), m));
    for(int i = 1; i <= m; ++i)
       Ans = add(Ans, pn1(mul(f[i], mul(mul(pre, suf[i + 1]), mul(invf[i], invf[m - i]))), m - i)),
    	pre = mul(pre, minus(n + 1, i));
    if(p ^ 1)	Ans = minus(f[1], mul(Ans, qpow(qpow(p, n), mod - 2)));
	
    printf("%d", Ans);
    return 0;
}

posted @   Martin_MHT  阅读(87)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示