多项式泛做2

多项式泛做2

卷积变换技巧

目标:将两个相乘的下标之和变为定值

  • 1. 若存在i=0nf[i]g[i]这样的式子

    f[i]=f[ni],则f[ni]=f[i],也就是把f[]翻转

    ​于是:i=0nf[i]g[i]=i=0nf[ni]g[i],下标和为n为定值,可以利用FFT加速卷积

  • 2.计算a[i]b[j],转换为多项式中xa[i]b[j],把b[j]变为b[j]+M(M=max{b}),保证指数为正

  • 3.计算a[1]b[x],a[2]b[x+1],a[3][x+2]...a[n][x1]这样循环的式子,

    a[n+1]=a[1],a[n+2]=a[2],...,a[2n]=a[n]

    b[1]=b[n],b[2]=b[n1],...,b[n]=b[1]

    则指数从n+12n的所有系数即为所有这样循环式子的结果。

  • 4.计算k阶前缀或差分

    A=a1+a2x+..+ann1,B=1+x+x2...+x=11x

    前缀:ck=i=0kai=i+j=kaibj(bj=1)

    1阶前缀:AB k阶前缀ABk=A1(1x)k

    Bki项的贡献为Ck+i1i

    后缀:cki+j=kaibj(b0=1,bi>0=1)

    i项贡献为Cki(1)i

AT2064 [AGC005F] Many Easy Problems(NTT)

Problem

给定一棵无根树,定义 f(i),对于所有大小为 i 的点集,求出能够包含它的最小连通块大小之和。对于 i=1n 的所有 i,求出 f(i),答案对924844033取模。(1N200000)

Sol

考虑对于一个点u,若移除这个点,就可以将原树分成若干个连通块,现在考虑点u对大小为k的连通块的贡献。点u包含在大小为k的连通块里,当且仅当其他k1个点存在至少2个在不同的连通块中,若k个点都在u分出的某一个连通块中,则点u肯定不会对这样的连通块有贡献。总结一下就是大小为k的点集不包含u当且仅当所有k个点都在以u为根的一个子树中。根据容斥,那么f(k)=u=1n(CnkvsonuCszvk)=n×Cnku=1nvsonuCszvk=n×Cnkj=knCjkvsonu[szv==j]。设g(k)=j=knCjkvsonu[szv==j]=j=knj!k!(jk)!vsonu[szv==j],令Ai=i!vsonu[szv==i]Bi=1i!,那么g(k)=1k!i=knAiBik,经典的变换技巧。

//大坑,这题的模数不是998244353,是924844033,这个原根是5
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;

const int N = 1100000;
const int p = 924844033, gg = 3, ig = 332738118, img = 86583718;//998244353 1004535809  
//924844033=>gg=5
const int M=18e5;
const int mod=924844033;
ll qpow(ll a, int b)
{
    ll res = 1;
    while(b) {
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return res;
}
namespace Poly{
  
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    //freopen("gen.in","r",stdin);
    Poly::init(20);
    ll n;
    cin>>n;

    vector<ll>fac(n+1),infac(n+1);
    fac[0]=1;
    for(ll i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    infac[n]=qpow(fac[n],mod-2);
    for(ll i=n;i>=1;i--)infac[i-1]=infac[i]*i%mod;
    vector<vector<int>>E(n+1);
    
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        E[u].push_back(v);
        E[v].push_back(u);
    }
    vector<ll>sz(n+1),cnt(n+1);
    auto dfs=[&](auto self,int u,int fa)->void{
        sz[u]=1;
        for(auto v:E[u])
        {
            if(v==fa) continue;
            self(self,v,u);
            sz[u]+=sz[v];
            cnt[sz[v]]++;
        }
        cnt[n-sz[u]]++;
    };
    dfs(dfs,1,0);
    
    auto binmo=[&](int x,int y)->ll{
         if(x<y) return 0;
         return fac[x]*infac[y]%mod*infac[x-y]%mod;
    };

    vector<ll>C(n+1);
    for(int i=1;i<=n;i++) C[i]=binmo(n,i)*n%mod;
    Poly::poly A(n+1),B(n+1);
    for(int i=1;i<=n;i++) A[i]=1LL*cnt[i]*fac[i]%mod;
    for(int i=0;i<=n;i++) B[i]=infac[i];

    reverse(A.begin(), A.end());
    Poly::poly G=Poly::poly_mul(A,B);
    G.resize(n+1);
    reverse(G.begin(), G.end());
    for(int i=1;i<=n;i++) G[i]=infac[i]*G[i]%mod;
    for(int i=1;i<=n;i++)
    cout<<(C[i]-G[i]+mod)%mod<<'\n';
    
    return 0;
    

}

Loj6503-「雅礼集训 2018 Day4」Magic【分治NTT]

Problem

桌面上摆放着m种魔术卡,共n 张,第i种魔术卡数量为ai,魔术卡顺次摆放,形成一个长度为n的魔术序列,在魔术序列中,若两张相邻魔术卡的种类相同,则它们被称为一个魔术对。
两个魔术序列本质不同,当且仅当存在至少一个位置,使得两个魔术序列这个位置上的魔术卡的种类不同,求本质不同的恰好包含k个魔术对的魔术序列的数量,答案对998244353取模。

Sol

题目要求恰好k个,套路地进行而二项式反演,令fi表示恰好i对,gi表示至少i对,那么fk=i=kn(1)ikCikgi,接下来考虑如何求gi。对于第i种魔术卡,如果不把它们分开,那么组合的时候就会贡献ai1对魔术对,现在考虑把第i种魔术卡切成几段,每切一段就会少1个魔术对,那么假设第i种魔术卡切了ki刀,切完后把颜色相同的段看成一个整体,然后就有ni=1mki个整体,把它们全排列,因为求的是至少,所以就算有相同颜色的段排在相邻位置也没有问题,由于是无标号的可重集,注意除相应的阶乘。生成函数F(x)=i=1mj=1aiCai1j1j!xj,则gi=(ni)!×[xni]F(x)。这里F(x)统计的是减少的魔术对的个数,本来如果不切的话有nm个魔术对,现在切了im次,就减少ni个魔术对。这里为什么i要减去m,是因为在算生成函数的时候算的是Cai1j1xj,也就是说第j项是切j1刀,总共有m种,最后的乘完的幂次i其实对应的是im,就减少ni个魔术对

//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N = 1100000;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//1004535809 
const int M=18e5;
const int mod=998244353;
template <typename T>void rd(T &x)
{
    x = 0;
     int f = 1;
     char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
    x *= f;
}
ll qpow(ll a, int b)
{
    ll res = 1;
    while(b) {
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return res;
}
namespace Poly
{
    #define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
    #define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
    #define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
    #define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了
    typedef vector<ll> poly;
    const int G = 3;//根据具体的模数而定,原根可不一定不一样!!!
    //一般模数的原根为 2 3 5 7 10 6
    const int inv_G = qpow(G, mod - 2);
    int RR[N], deer[2][21][N], inv[N];

    void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间
        for(int p = 1; p <= t; ++ p) {
            int buf1 = qpow(G, (mod - 1) / (1 << p));
            int buf0 = qpow(inv_G, (mod - 1) / (1 << p));
            deer[0][p][0] = deer[1][p][0] = 1;
            for(int i = 1; i < (1 << p); ++ i) {
                deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//逆
                deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;
            }
        }
        inv[1] = 1;
        for(int i = 2; i <= (1 << t); ++ i)
            inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
    }
    int NTT_init(int n) {//快速数论变换预处理
        int limit = 1, L = 0;
        while(limit < n) limit <<= 1, L ++ ;
        for(int i = 0; i < limit; ++ i)
            RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
        return limit;
    }
    void NTT(poly &A, int type, int limit) {//快速数论变换
        A.resize(limit);
        for(int i = 0; i < limit; ++ i)
            if(i < RR[i])
                swap(A[i], A[RR[i]]);
        for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {
            int len = mid >> 1;
            for(int pos = 0; pos < limit; pos += mid) {
                int *wn = deer[type][j];
                for(int i = pos; i < pos + len; ++ i, ++ wn) {
                    int tmp = 1ll * (*wn) * A[i + len] % mod;
                    A[i + len] = ck(A[i] - tmp + mod);
                    A[i] = ck(A[i] + tmp);
                }
            }
        }
        if(type == 0) {
            for(int i = 0; i < limit; ++ i)
                A[i] = 1ll * A[i] * inv[limit] % mod;
        }
    }
    inline poly poly_mul(poly A, poly B) {//多项式乘法
        int deg = A.size() + B.size() - 1;
        int limit = NTT_init(deg);

        poly C(limit);
        NTT(A, 1, limit);
        NTT(B, 1, limit);
        for(int i = 0; i < limit; ++ i)
            C[i] = 1ll * A[i] * B[i] % mod;
        NTT(C, 0, limit);
        C.resize(deg);
        return C;
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    Poly::init(19);
    int m,n,k;
    cin>>m>>n>>k;
    vector<int>a(m+1);
    for(int i=1;i<=m;i++) cin>>a[i];
    vector<ll>fac(n+1),infac(n+1);
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    infac[n]=qpow(fac[n],mod-2);
    for(int i=n;i>=1;i--) infac[i-1]=infac[i]*i%mod;

    vector<vector<ll>>g(m+1);
    auto C=[&](int x,int y)->ll{
      if(x<y) return 0;
      return fac[x]*infac[y]%mod*infac[x-y]%mod;
    };
    for(int i=1;i<=m;i++)
    {
        g[i].push_back(0);
        for(int j=1;j<=a[i];j++)
            g[i].push_back(C(a[i]-1,j-1)*infac[j]%mod);
    }
    auto CDQ=[&](auto self,int l,int r)->Poly::poly{

        if(l==r)  return g[l];
        int mid=l+r>>1;
        Poly::poly L=self(self,l,mid);
        Poly::poly R=self(self,mid+1,r);
        Poly::poly Q=Poly::poly_mul(L,R);
        return Q;
    };
    Poly::poly F=CDQ(CDQ,1,m);
    Poly::poly G((int)F.size());
    for(int i=0;i<=n;i++) G[i]=F[n-i]*fac[n-i]%mod;
    ll ans=0;
    for(int i=k;i<=n;i++)
    {
        if((i-k)&1) ans=(ans+mod-C(i,k)*G[i]%mod)%mod;
        else ans=(ans+C(i,k)*G[i]%mod)%mod;
    }
    cout<<ans<<'\n';
}

P4491 [HAOI2018]染色(二项式反演+NTT)

Problem

为了报答小 C 的苹果, 小 G 打算送给热爱美术的小 C 一块画布, 这块画布可 以抽象为一个长度为 N 的序列, 每个位置都可以被染成 M 种颜色中的某一种.然而小C只关心序列的 N 个位置中出现次数恰好为 S 的颜色种数, 如果恰好出现了 S 次的颜色有 K 种, 则小 C 会产生 Wk的愉悦度.小C希望知道对于所有可能的染色方案, 他能获得的愉悦度的和对 1004535809 取模的结果是多少.

Sol

二项式反演,设fi表示恰好出现了i次,gi表示至少出现了i次,则fk=i=km(1)ikCikgi。考虑求gi,由组合数学的思想算出gi=CmiAnS×i(S!)S×i(mi)nS×i,组合意义就是先从m种颜色取i种,使得每一种涂S次,然后考虑选排列,从n个物品种选出S×i个,然后全排列,由于每种颜色有多个,相当于是可重复集的排列,每种颜色要除以S!,总共要除(S!)i,然后剩下的mi种颜色随便涂在剩下的nS×i个物品上。注意到fk=i=km(1)ikCikgi=i=km(1)iki!k!(ik)!gi,令Ai=i!giBi=(1)ii!,那么fk=1k!i=kmAiBik,卷积形式,这样就可加快运算,最后答案就是i=1min(m,nS)wifi

//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;

const int N = 1100000;
const int p = 1004535809, gg = 3, ig = 332738118, img = 86583718;//998244353 1004535809 
const int M=18e5;
const int mod=1004535809;
template <typename T>void rd(T &x)
{
    x = 0;
     int f = 1;
     char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
    x *= f;
}
ll qpow(ll a, int b)
{
    ll res = 1;
    while(b) {
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return res;
}
namespace Poly{
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    Poly::init(19);
    int n,m,s;
    cin>>n>>m>>s;
    int L=max(n,m);
    vector<ll>w(m+1);
    for(int i=0;i<=m;i++) cin>>w[i];
    vector<ll>fac(L+1),infac(L+1);
    fac[0]=1;
    for(int i=1;i<=L;i++) fac[i]=fac[i-1]*i%mod;
    infac[L]=qpow(fac[L],mod-2);
    for(int i=L;i>=1;i--) infac[i-1]=infac[i]*i%mod;

    auto C=[&](int x,int y)->ll{
        if(x<y) return 0;
        return fac[x]*infac[x-y]%mod*infac[y]%mod;
    };
    auto P=[&](int x,int y)->ll{
        if(x<y) return 0;
        return fac[x]*infac[x-y]%mod;
    };
    int L_=min(m,n/s);
    Poly::poly A(L_+1),B(L_+1);
    auto g=[&](int i)->ll{
        return C(m,i)*P(n,s*i)%mod*qpow(infac[s],i)%mod*qpow(m-i,n-s*i)%mod;
    };
    for(int i=0;i<=L_;i++)
    {
        A[i]=fac[i]*g(i)%mod;
        if(i&1) B[i]=(mod-infac[i])%mod;
        else B[i]=infac[i];
    }
    
    reverse(A.begin(), A.end());
    Poly::poly F=Poly::poly_mul(A,B);
    F.resize(L_+1);
    reverse(F.begin(), F.end());
    ll ans=0;
    for(int i=0;i<=L_;i++)
    ans=(ans+infac[i]*F[i]%mod*w[i]%mod)%mod;
    if(ans<0) ans=(ans+mod)%mod;
    cout<<ans<<'\n';

    return 0;
    

}

P5644 [PKUWC2018]猎人杀

Problem

一开始有n个猎人,第ii个猎人有仇恨度wi ,每个猎人只有一个固定的技能:死亡后必须开一枪,且被射中的人也会死亡。然而向谁开枪也是有讲究的,假设当前还活着的猎人有[i1,...,im],那么有wikj=1nwij的概率向猎人ik开枪。一开始第一枪由你打响,目标的选择方法和猎人一样,由于开枪导致的连锁反应,所有猎人最终都会死亡,现在 1 号猎人想知道它是最后一个死的的概率。答案对 998244353 取模。

Sol

首先对题意进行一波转换,设S=i=1nwi,则题意可以转为为:每个人被打中的概率为wiS,不断开枪直至打中一个活着的人,则这个人就会死去。什么意思呢?就是说就算每轮我们允许向一个死去的人开枪,但最终第i个活着的人被射中的概率是不变的,这样就可以不用处理每次射击分母都改变的情况了,证明:

假设当前还存活的猎人的仇恨度为P,并且第i个活着的猎人被射中的概率为p,题意转换前p=wiP,题意转换后p=i=1(SPS)i1wiS=wiS11SPS=wiSSP=wiP

接下来考虑容斥,枚举在1号猎人之后死的猎人的集合T,并且该集合猎人总的仇恨值为P,全部猎人总的仇恨值为S,那么ans=T(1)|T|w1Si=1(1P+w1S)i=T(1)|T|w1S1P+w1S=T(1)|T|w1P+w1

由于i=1nwi100000,所以考虑枚举P,问题就只需要在已知P的情况下如何快速求出有多少个集合的仇恨值和为P,考虑生成函数F(x)=i=2n(1xwi)P的贡献就是[xP]F(x),所以答案就是ans=P=0Sw1[xP]F(x)w1P+w1

//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N = 5100000;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;//998244353 1004535809  
//924844033=>gg=5
const int M=18e5;
const int mod=998244353;
ll qpow(ll a, int b)
{
    ll res = 1;
    while(b) {
        if(b & 1) res = 1ll * res * a % mod;
        a = 1ll * a * a % mod;
        b >>= 1;
    }
    return res;
}
namespace Poly{   
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    //freopen("gen.in","r",stdin);
    Poly::init(20);
    ll n;
    cin>>n;

    vector<int>w(n+1);
    int S=0;
    for(int i=1;i<=n;i++) cin>>w[i],S+=w[i];
    auto CDQ=[&](auto self,int l,int r)->Poly::poly{
        if(l==r)
        {
            Poly::poly F(w[l]+1);
            F[0]=1,F[w[l]]=mod-1;
            return F;
        }
        int mid=l+r>>1;
        Poly::poly L=self(self,l,mid);
        Poly::poly R=self(self,mid+1,r);
        return Poly::poly_mul(L,R);
    };
    Poly::poly F=CDQ(CDQ,2,n);
    ll ans=0;
    for(int i=0;i<=S-w[1];i++)
    if(F[i]<0) ans=(ans+mod+F[i]*w[1]%mod*qpow(i+w[1],mod-2)%mod)%mod;
    else ans=(ans+F[i]*w[1]%mod*qpow(i+w[1],mod-2)%mod)%mod;
    cout<<ans<<'\n';
    return 0;
}

CF960G-Bandit Blues[第一类斯特林数,分治,NTT]

Problem

给你三个正整数 nab,定义 A 为一个排列中是前缀最大值的数的个数,定义 B 为一个排列中是后缀最大值的数的个数,求长度为 n 的排列中满足 A=aB=b 的排列个数。n105 ,答案对 998244353取模。

Sol

考虑最大数放的位置为i,这时候排列被分成2部分,然后在剩下的n1个数选i1个放左边,选ni个放右边,方法数为Cn1i1。然后先考虑左边,由于最大的数一定会同时存在于前缀最大和后缀最大,所以前i1个数有A1个最大前缀,考虑一种构造方法,先把这i1个数分成A1组,然后每组把最大的数放到最左边,最后把按照每组最左边数的大小按照从小到大的顺序放置。也可以理解为就是一种圆排列,因为是循环的,所以我们每次把这个圆环从最大的数断开,方法数是[i1A1]。同理右边就是[niB1],最后答案就是i=1n(n1i1)[i1A1][niB1]

2021牛客暑期多校训练营9 C Cells

题意

给定n个点,分别在(0,a1),(0,a2)...(0,an),现在这n个点只能向右或向下移动,要求这n个点分别走到(1,0),(2,0)...(n,0)且路径两两不相交的总方案数,注意(0,ai)可以走到(1,0),(2,0)...(n,0)中的任何一个。

Sol

首先不相交路径数我们优先考虑LGV引理(当时不会,大雾)
在这里插入图片描述

根据LGV​,由于每个(0,ai)​到(1,0),(2,0)...(n,0)​的路径数分别为Cai+11,Cai+22,...,Cai+nn

也即(ai+1)!1!ai!,(ai+2)!2!ai!,...,(ai+n)!n!ai!

那么所对应的答案矩阵为

(5)M=[(a1+1)!1!a1!(a1+2)!2!a1!(ai+n)!n!an!(a2+1)!1!a2!(a2+2)!2!a2!(a2+n)!n!a2!(an+1)!1!an!(an+2)!2!an!(an+n)!n!an!]

然后每一列提出个1i!出来,再把上下约分一下

(5)M=i=1n1i![(a1+1)(a1+1)(a1+2)(a1+1)(a1+2)...(a1+n)(a2+1)(a2+1)(a2+2)(a2+1)(a2+2)...(a2+n)(an+1)(an+1)(an+2)(an+1)(an+2)...(an+n)]

以第一行为例

(a1+1)(a1+2)(a1+1)=(a1+1)2

(a1+1)(a1+2)(a1+3)3(a1+1)(a1+2)+(a1+1)=(a1+1)3

以此类推:每一列都可以通过前几列华为(a1+1)i的形式,于是

(5)M=i=1n1i![(a1+1)(a1+1)2(a1+1)n(a2+1)(a2+1)2(a2+1)n(an+1)(an+1)2(an+1)n]

然后每行提一个公因式出来

(5)M=i=1n1i!i=1n(ai+1)[1(a1+1)(a1+1)n11(a2+1)(a2+1)n11(an+1)(an+1)n1]

到这里就很清楚了,后面的行列式是一个范德蒙行列式

M=i=1n1i!i=1n(ai+1)1i<jnn((aj+1)(ai+1))=i=1n1i!i=1n(ai+1)1i<jnn(ajai)

于是用O(n)处理处i=1n1i!i=1n(ai+1)

后面差的形式如果暴力则O(n2),显然不可,考虑用卷积(FFT/NTT)​ ,优化到O(nlogn)

为什么可以这样?一般的卷积是求系数相乘,怎么才能求减法呢?

由于卷积相乘幂次是相加的,于是可以把aiai当做幂次,这样卷积完之后,遍历幂次的所有范围,如果系数不为0,说明这个幂次存在也即(ajai)存在。

下面再考虑一个问题,卷积求的是任意两个的乘积,而我们要求的是有顺序的1i<jn

首先由于存在负数,于是我们把负数加上一个偏移量(ai的最大值记为M),于是我们幂次有ai
Mai ,而我们最后卷积完会得到的所有幂次范围为[0,2M] ,这是注意题目有个条件1i<jn,ai<aj​,于是这样卷完(ajai)等价于aj+Mai>M 于是所有符合的幂次我们就从M+12M枚举,其对应的系数就为这个差值出现的次数,快速幂求一下就可以了。

#include <bits/stdc++.h>
#define ll long long
#define lll __int128
using namespace std;
const  int P = 998244353,G=3; // P=1004535809 ord(P)=3
const int pi=acos(-1);
inline ll read()
{
     ll x = 0, f = 1;
     char ch = getchar();
    while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}

const int N=1e6+10,M=1e6;
ll n,m;
ll rev[1<<22],bit,tot;
ll a[1<<22],b[1<<22];
ll qmi(ll x,ll y)
{
    ll res=1;
    while(y)
    {
        if(y&1) res=res*x%P;
        x=x*x%P;
        y>>=1;
    }
    return res;
}
ll inv(ll x)
{
    return qmi(x,P-2);
}
void NTT(ll a[],int type)
{
    for(int i=0;i<tot;i++)
    if(i<rev[i]) swap(a[i],a[rev[i]]);

    for(int mid=1;mid<tot;mid<<=1)
    {
        ll wn=qmi(G,(P-1)/(mid*2));
        if(type==-1) wn=qmi(wn,P-2);
        for(int i=0,len=mid<<1;i<tot;i+=len)
        {
            ll w=1;
            for(int j=0;j<mid;j++,w=w*wn%P)
            {
                ll x=a[i+j],y=w*a[i+j+mid]%P;
                a[i+j]=(x+y)%P,a[i+j+mid]=(x-y+P)%P;
            }
        }
    }
    if(type==-1)
    {
        ll inv_=inv(tot);
        for(int i=0;i<tot;i++)
        a[i]=a[i]*inv_%P;
    }
}
int main()
{
     n=read();
    ll ans=1;
    for(int i=1;i<=n;i++)
    {
        ll x;
        x=read();
        ans=ans*(x+1)%P;
        a[x]=1,b[M-x]=1;
    }
    ll k1=1,k2=1;
    for(int i=1;i<=n;i++)
    {
        k1=k1*i%P;
        k2=k2*k1%P;
    }
    ans=ans*inv(k2)%P; 
    //cout<<ans<<endl;

    tot=1<<21,bit=21;
    for(int i=0;i<tot;i++) 
    rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    NTT(a,1),NTT(b,1);
    for(int i=0;i<tot;i++) a[i]=a[i]*b[i]%P;
    NTT(a,-1);
      
    for(int i=1e6+1;i<=M*2;i++)
    {
        if(a[i])
        {  
            ans=ans*qmi(i-1e6,a[i])%P; //求的是 (a_j - a_i ) (0<i<j<=n&&a_i<a_j) 
                                       //所以枚举>M的数就是(a_j-a_i)
        }
    }
    cout<<ans<<endl;
    puts("");
    
    return 0;

    
}

posted @   Arashimu  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示