2022牛客多校第四场C.Easy Counting Problem(EGF+NTT)

题意:给一个字符集,大小不超过10,第i个字符出现次数不少于ci,求字符串长度为n的方案数,至多300个询问,ci<=5000,n<=1e7
(不复制图片了,要钱的比赛不知道会不会被举办==)
思路:指数生成函数
由egf定义,第k个字符的egf为 f ( k ) = ∑ c k + o o x i i ! f(k)=\sum_{c_k}^{+oo}\frac{x^i}{i!} f(k)=ck+ooi!xi
则答案为 [ x n n ! ] ∏ k = 1 w f ( k ) , w 为字符集大小 [\frac{x^n}{n!}]\prod_{k=1}^w{f(k)},w为字符集大小 [n!xn]k=1wf(k)w为字符集大小
但是n比较大,直接卷肯定不行。改写一下f(k)的形式
f ( k ) = e x p ( x ) − ∑ i = 0 c k − 1 x i i ! f(k)=exp(x)-\sum_{i=0}^{c_k-1}\frac{x^i}{i!} f(k)=exp(x)i=0ck1i!xi
可以将exp(x)看成一个整体,最后的答案的形式是
∑ i = 0 w g ( i ) ∗ e x p ( x ) i = ∑ i = 0 w g ( i ) ∗ e x p ( i x ) , 其中 g ( i ) 是一个多项式 \sum_{i=0}^wg(i)*exp(x)^i=\sum_{i=0}^wg(i)*exp(ix),其中g(i)是一个多项式 i=0wg(i)exp(x)i=i=0wg(i)exp(ix),其中g(i)是一个多项式
g(i)最高次数不超过 ∑ c i \sum{c_i} ci,而exp(ix)的每项系数都是已知,那么只要遍历每个g(i)项的系数乘上exp(ix)对应项系数即可。最后别忘了乘n!
具体写法可以借鉴dp的思路,设dpij表示前i个f相乘得到的式子exp(jx)的系数是什么,存的是个多项式
转移方程 d p i , j = d p i − 1 , j − d p i , j − 1 ∗ ∑ k = 0 c i − 1 x k k ! dp_{i,j}=dp_{i-1,j} - dp_{i,j-1}*\sum_{k=0}^{c_i-1}\frac{x^k}{k!} dpi,j=dpi1,jdpi,j1k=0ci1k!xk,其实就是模拟多项式暴力算罢了,这样思路清楚一点。
复杂度 O ( w 2 s l o g s + q w s ) , s = ∑ c i O(w^2slogs+qws),s=\sum{ci} O(w2slogs+qws),s=ci

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define IOS ios::sync_with_stdio(false),cin.tie(nullptr) 
#define _for(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define _rep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define pii pair<int ,int >
#define pb(v) push_back(v)
#define all(v) v.begin(),v.end()
#define int long long
#define inf 0x3f3f3f3f
#define INF 0x7f7f7f7f7f7f7f7f
#define endl "\n"
#define fi first
#define se second
#define ls p<<1
#define rs p<<1|1
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
#define AC return 0
#define ldb long double
const int maxn = 6e6+10;
const int p = 998244353;
const int mod = 998244353;
int Pow(int x,int d){
    int tans = 1;
    if(d == 0)return 1%p;
    int a = Pow(x,d/2);
    tans = 1ll*a*a%p;
    if(d%2)tans = 1ll*tans*x%p;
    return tans%p;
}
typedef vector<int> Poly;//多项式定义
inline Poly  operator + (const Poly&A ,const Poly&B){
    Poly res (max(A.size(),B.size()),0);
    for(int i=0 ;i<max(A.size(),B.size()) ;i++){
        if( i < A.size() && i<B.size() ) res[i] = (A[i]+B[i])%mod;
        else if( i >= A.size() ){
            res[i] = B[i];
        }
        else res[i] = A[i];
    }
    return res;
}
inline Poly  operator - (const Poly&A ,const Poly&B){
    Poly res (max(A.size(),B.size()),0);
    for(int i=0 ;i<max(A.size(),B.size()) ;i++){
        if( i < A.size() && i<B.size() ) res[i] = (A[i]-B[i]+mod)%mod;
        else if( i >= A.size() ){
            res[i] = (mod-B[i])%mod;
        }
        else res[i] = A[i];
    }
    return res;
}
int F1[maxn],F2[maxn];
int rev[maxn];
void NTT(int * A,int lim,int opt) {
    int i, j, k, m, gn, g, tmp;
    for(int i = 0; i < lim; ++i)rev[i] = (i & 1)*(lim >> 1) + (rev[i >> 1] >> 1);
    for(i = 0; i < lim; ++i)if (rev[i] < i) swap(A[i], A[rev[i]]);
    for(m = 2; m <= lim; m <<= 1) {
        k = m >> 1;
        gn = Pow(3,(p - 1) / m);
        for(i = 0; i < lim; i += m) {
            g = 1;
            for (j = 0; j < k; ++j, g = 1ll * g * gn % p) {
                tmp = 1ll * A[i + j + k] * g % p;
                A[i + j + k] = (A[i + j] - tmp + p) % p;
                A[i + j] = (A[i + j] + tmp) % p;
            }
        }
    }
    if(opt == -1){
        reverse(A+1,A+lim);
        int inv = Pow(lim,p-2);
        for(i = 0; i < lim; ++i) A[i] = 1ll * A[i] * inv % p;
    }
}
Poly mul(const Poly & A,const Poly & B){
    int n = A.size(),m = B.size();
    int siz = n + m - 1;
    Poly C(siz);
    if(siz < 64){//小于等于64项直接暴力算
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++)C[i+j] = (C[i+j] + 1LL*A[i]*B[j]%p)%p;
        }
        return C;
    }
    int fsiz = 1;
    while(fsiz <= siz)fsiz *= 2;
    for(int i = 0;i < fsiz;i++)F1[i] = F2[i] = 0;
    for(int i = 0;i < n;i++)F1[i] = A[i];
    for(int i = 0;i < m;i++)F2[i] = B[i];
    NTT(F1,fsiz,1);
    NTT(F2,fsiz,1);
    for(int i = 0;i < fsiz;i++)F1[i] = 1ll*F1[i]*F2[i]%p;
    NTT(F1,fsiz,-1);
    for(int i = 0;i < siz;i++){
        C[i] = F1[i];
    }
    return C;
}
const int N = 5e5+10;
const int M = 1e7+5;
int W,n;
int c[11],fac[M],ni_f[M];
int mi[11][M];
Poly f[11][11];
ll qsm(int a,int b){
    a %= mod;
    ll ans=1,tmp=a;
    while( b ){
        if( b&1 ) ans = (ans * tmp)%mod;
        tmp = tmp * tmp%mod;
        b>>=1;
    }
    return ans;
}
void ini(){
    int mx = 1e7;
    fac[0]=1;
    _for(i,1,mx) fac[i] = fac[i-1]*i%mod;
    ni_f[mx] = qsm(fac[mx],mod-2);
    _rep(i,mx-1,0) ni_f[i] = ni_f[i+1]*(i+1)%mod;
}
int S;
void ff(Poly F){
    F.resize(min(S+1,(int)F.size()));
}
void solve(){
    f[1][1].resize(1,1);
    f[1][0].resize(c[1]+1,0);
    _for(i,0,c[1]-1){
        f[1][0][i] = (mod-ni_f[i])%mod;
    }
    _for(i,2,W){
        _for(j,0,i){
            Poly t (c[i]+1,0);
            _for(k,0,c[i]-1){
                t[k] = (mod-ni_f[k])%mod;
            }
            f[i][0] = mul(f[i-1][0],t);
            ff(f[i][0]);
            f[i][j] = (f[i-1][j-1] + mul(f[i-1][j],t));
            ff(f[i][j]);
        }
    }
}
int Q(int n){
    int ans=0;
    for(int i=0 ;i<=W ;i++){// exp(ix)
        int pw = qsm(i,n-min(n,(int)f[W][i].size()-1ll));
        for(int j=min(n,(int)f[W][i].size()-1ll) ;j>=0 ;j--){
            int t = n-j;
            if( j < min(n,(int)f[W][i].size()-1ll) ) pw = pw * i%mod;
            ans = (ans + f[W][i][j] * pw %mod*ni_f[t]%mod)%mod;
        }
    }
    return ans*fac[n]%mod;
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif  
    IOS;
    cin>>W;
    _for(i,1,W) cin>>c[i];
    S = accumulate(c+1,c+1+W,0ll);
    ini();
    solve();
    int q;cin>>q;
    while( q-- ){
        int x;cin>>x;
        int ans = Q(x);
        cout<<ans<<endl;
    }
    AC; 
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值