LOJ #6261. 一个人的高三楼 NTT

题目链接


题意:求一个长度为\(n\)的数列的\(k\)阶前缀和对998244353取模的结果.\((n\le10^{5}\ \ k\le2^{60})\)

\[f_k(x)=\sum_{i=0}^{n-1}S_{i+1}^{(k)}x^i\]

\[g(x)=\sum_{i=0}^{n-1}x^i\]

于是有\[f_k(x)g(x)\equiv f_{k+1}(x)\pmod{x^n}\]

所求即\[g(x)^kf_0(x)\]

直接快速幂 复杂度\(O(n\ log\ n\ log\ k)\) 无法通过此题

考虑\(g(x)^k\)各项的意义,设\[g(x)^k=\sum_{i=0}^{n-1}a_ix^i\]

\(a_i\)即为有序地选k个\([0,n-1]\)之间的整数,和为i的方案数,用插板法可得\(a_i=\binom{k-1+i}{i}\),从而可以\(O(n)\)\(O(n\ log\ n)\)得到\(g(x)^k\)

最后用\(NTT\)\(f_0(x)\)\(g(x)^k\)合并

总复杂度\(O(n\ log\ n)\)

#include<cstdio>
#include<algorithm>
#include<ctype.h>
#include<string.h>
#include<math.h>

using namespace std;
#define ll long long
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define travel(i,x) for(int i=h[x];i;i=pre[i])

inline char read() {
    static const int IN_LEN = 1000000;
    static char buf[IN_LEN], *s, *t;
    return (s == t ? t = (s = buf) + fread(buf, 1, IN_LEN, stdin), (s == t ? -1 : *s++) : *s++);
}
template<class T>
inline void read(T &x) {
    static bool iosig;
    static char c;
    for (iosig = false, c = read(); !isdigit(c); c = read()) {
        if (c == '-') iosig = true;
        if (c == -1) return;
    }
    for (x = 0; isdigit(c); c = read()) x = ((x + (x << 2)) << 1) + (c ^ '0');
    if (iosig) x = -x;
}
const int OUT_LEN = 10000000;
char obuf[OUT_LEN], *ooh = obuf;
inline void print(char c) {
    if (ooh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), ooh = obuf;
    *ooh++ = c;
}
template<class T>
inline void print(T x) {
    static int buf[30], cnt;
    if (x == 0) print('0');
    else {
        if (x < 0) print('-'), x = -x;
        for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
        while (cnt) print((char)buf[cnt--]);
    }
}
inline void flush() { fwrite(obuf, 1, ooh - obuf, stdout); }
const int N = 1<<18, P = 998244353;
ll k;
int n, p, a[N], b[N];
inline int Pow(ll x, int y=P-2){
    ll ass=1;
    for(; y; y>>=1, x=x*x%P) if(y&1) ass=ass*x%P;
    return ass;
}
inline void NTT(int *f, int g){
    for(int i=0, j=0; i<p; ++i){
        if(i>j) swap(f[i], f[j]);
        for(int k=p>>1; (j^=k)<k; k>>=1);
    }
    for(int i=1; i<p; i<<=1){
        int w0=(g==1?Pow(3, (P-1)/i/2):Pow(Pow(3, (P-1)/i/2)));
        for(int j=0; j<p; j+=i<<1){
            int w=1;
            for(int k=j; k<j+i; ++k){
                int t=(ll)w*f[k+i]%P;
                f[k+i]=(P+f[k]-t)%P;
                f[k]=(f[k]+t)%P;
                w=(ll)w*w0%P;
            }
        }
    }
    if(g==-1){
        int I=Pow(p);
        rep(i, 0, p-1) f[i]=(ll)f[i]*I%P;
    }
}
int main() {
    read(n), read(k);
    rep(i, 0, n-1) read(a[i]);
    b[0]=1;
    rep(i, 1, n-1) b[i]=(k-1+i)%P*b[i-1]%P*Pow(i)%P;
    for(p=1; p<n*2-1; p<<=1);
    NTT(a, 1), NTT(b, 1);
    rep(i, 0, p-1) a[i]=(ll)a[i]*b[i]%P;
    NTT(a, -1);
    rep(i, 0, n-1) print(a[i]), print('\n');
    return flush(), 0;
}

转载于:https://www.cnblogs.com/CMXRYNP/p/9406169.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值