WC2019 数树 和 UOJ335 生成树计数

数树

本题包含三个问题:

  • 问题 0:已知两棵 \(n\) 个节点的树的形态(两棵树的节点标号均为 \(1\)\(n\)),其中第一棵树是红树,第二棵树是蓝树。要给予每个节点一个 \([1, y]\) 中的整数,使得对于任意两个节点 \(p, q\),如果存在一条路径 \((a_1 = p, a_2, \cdots , a_m = q)\) 同时属于这两棵树,则 \(p, q\) 必须被给予相同的数。求给予数的方案数。

    • 存在一条路径同时属于这两棵树的定义见「题目背景」。
  • 问题 1:已知蓝树,对于红树的所有 \(n^{n−2}\) 种选择方案,求问题 0 的答案之和。

  • 问题 2:对于蓝树的所有 \(n^{n−2}\) 种选择方案,求问题 1 的答案之和。

提示:\(n\) 个节点的树一共有 \(n^{n−2}\) 种。

在不同的测试点中,你将可能需要回答不同的问题。我们将用 \(\text{op}\) 来指代你需要回答的问题编号(对应上述 0、 1、 2)。

由于答案可能很大,因此你只需要输出答案对 \(998, 244, 353\) 取模的结果即可。

所有测试点均满足 \(3 \le n \le 10^5, 1 \le y \lt 998244353, \text{op} \in \{0, 1, 2\}\)

问题 0

算一算两树的公共边数即可。

连接的路径相同说明路径上的边都是共有的边。贡献折算下来一条公共边会导致自由变量的个数减一。

set统计,时间复杂度 \(O(n \log n)\)

namespace Subtask0{
    set<pair<int,int> > edge;
    
    void main(int n,int y){
        for(int i=1;i<n;++i){
            int u=read<int>(),v=read<int>();
            if(u>v) swap(u,v);
            edge.emplace(u,v);
        }
        int cnt=n;
        for(int i=1;i<n;++i){
            int u=read<int>(),v=read<int>();
            if(u>v) swap(u,v);
            if(edge.count(make_pair(u,v))) --cnt;
        }
        printf("%d\n",fpow(y,cnt));
    }
}

问题 1

考虑枚举蓝树上的一个边集 \(S\),强制红树上同样存在这些边,计算将剩余 \(n-|S|\) 个连通块连成一棵树的方案数,更新答案。

问题 0可知,我们只关心公共边的数量。

上述算法中枚举大小为 \(S\) 的边集计算的时候,一个恰好包含 \(T\) 条蓝边的红树会被计算 \(\binom{T}{S}\) 次,并且由于最终的生成树上的蓝边数尚不确定,我们也无法得知需要乘上 \(y\) 的多少次方。

注意到

\[ y^{-T}=(y^{-1}-1+1)^T\\ =\sum_{S=0}^T\binom{T}{S}(y^{-1}-1)^S \]

若看作每选取一条蓝边产生 \(\times (y^{-1}-1)\) 的贡献,我们就可以直接应用上述算法,只需将最终答案乘以 \(y^n\) 即可。

这个构造简直绝了。我做容斥题第一次见到逆向运用二项式定理的。

总方案数除以被限制的个数也是一种常用的套路。

由Prufer序列,将 \(m\) 个大小分别为 \(a_1,a_2,\dots,a_m~(\sum a_i=n)\) 的连通块连成一棵树的方案数为 \(n^{m-2}\prod a_i\)\(n^{m-2}\) 部分的贡献可以看做每选取一条蓝边产生 \(\times n^{-1}\) 的贡献,最后将答案乘以 \(n^{n-2}\)\(\prod a_i\) 部分的贡献对应的组合意义为在每一个连通块内选择恰好一个代表点的方案数。

我们给每个连通块分配一个标号,然后把这个标号拿去做Prufer序列。

假设生成了一个长度为 \(m-2\) 的序列 \(P\),那么方案数为 \(\prod_{i=1}^m a_i^{t_i+1}\)。其中 \(t_i\) 表示 \(i\) 号连通块在序列 \(P\) 中的出现次数。\(i\) 总共连了 \(deg_i=t_i+1\) 条边,而一次在 \(i,j\) 之间的连边的贡献是 \(a_i\times a_j\),所以方案数是那个连乘式。

因此总方案数为

\[ tot=\sum_{P}\prod_{i=1}^m a_i^{t_i+1}\\ =\prod_{i=1}^m a_i\sum_{P}\prod_{i=1}^m a_i^{t_i} \]

考虑右边那个求和的意义。相当于给你 \(m-2\) 个空,每个空里面可以填 \(a_1\sim a_m\),一种填法的贡献是所有空里面填的数的乘积,然后我们要对所有填法的贡献求和。

那么总方案数可以认为是

\[ tot=\prod_{i=1}^m a_i (\sum_{i=1}^ma_i)^{m-2}\\ =n^{m-2} \prod_{i=1}^m a_i \]

又是一波构造。给公式赋予比较容易处理的组合意义,第一次见到运用。

生成树的数量是 \(n^{n-2}\),而又有 \(n-m\) 条蓝边,所以给每条蓝边分配一个 \(n^{-1}\) 的贡献,这样最后在外面乘上一个 \(n^{n-2}\) 就可以凑出 \(n^{m-2}\)

至此,我们可以设计一个简单树形DP来加速上述枚举算法。

\(dp(i,0/1)\) 表示 \(i\) 所在连通块有没有选出代表点的情况下,其子树内上述算法所有情况的贡献总和,可以 \(O(1)\) 简单转移。

\[ w=(y^{-1}-1)n^{-1}\\ dp(u,0)=dp(u,0)\times dp(v,1)+dp(u,0)\times dp(v,0)\times w\\ dp(u,1)=dp(u,0)\times dp(v,1)\times w+dp(u,1)\times dp(v,0)\times w+dp(u,1)\times dp(v,1) \]

时间复杂度 \(O(n)\)

namespace Subtask1{
    CO int N=100000+10;
    vector<int> to[N];
    int weight,dp[N][2];
    
    void dfs(int u,int fa){
        dp[u][0]=dp[u][1]=1;
        for(int v:to[u])if(v!=fa){
            dfs(v,u);
            int res[2];
            res[0]=mul(dp[u][0],dp[v][1]);
            res[0]=add(res[0],mul(dp[u][0],mul(dp[v][0],weight)));
            res[1]=mul(dp[u][0],mul(dp[v][1],weight));
            res[1]=add(res[1],mul(dp[u][1],mul(dp[v][0],weight)));
            res[1]=add(res[1],mul(dp[u][1],dp[v][1]));
            dp[u][0]=res[0],dp[u][1]=res[1];
        }
    }
    void main(int n,int y){
        for(int i=1;i<n;++i){
            int u=read<int>(),v=read<int>();
            to[u].push_back(v),to[v].push_back(u);
        }
        weight=mul(add(fpow(y,mod-2),mod-1),fpow(n,mod-2));
        dfs(1,0);
        int ans=mul(dp[1][1],mul(fpow(y,n),fpow(n,n-2)));
        printf("%d\n",ans);
    }
}

问题 2

问题 1的算法,我们可以设计一个简单DP来完成该问题。

\(f_i\) 表示共有 \(i\) 个点的情况下,问题 1的枚举算法所有情况的贡献总和。

枚举 \(1\) 号点所在连通块大小 \(j\) ,则有:

\[ f_i=\sum_{j=1}^i\binom{i-1}{j-1}\times f_{i-j}\times j^2\times j^{j-2} \times (n^{-2}(y^{-1}-1))^{j-1} \]
最终答案即为 \(ans=f_n\times y^n \times n^{2n-4}\)

红蓝树此时等价。我们还是去统计每个连通块的贡献,但是由于此时对于连通块的限制只有点数,所以我们的连通块其实就是任意的生成树。

此时我们需要在两个图中把所有连通块连成一棵树,所以边的代价变化了 \(\times n^{-2}\)。代表点也需要在两个图中都选一个。

直接使用分治NTT优化DP,时间复杂度 \(O(n \log^2 n)\)

考场上集训队选手的最佳做法。

\(F(x)\)\(f\) 的指数型生成函数,

\[ coef=\sum_{i=0}^\infty(i+1)^2\times (i+1)^{i-1}\times (n^{-2}(y^{-1}-1))\times \frac{x^i}{i!} \]

上述转移方程即

\[ F'=F\times coef\\ \frac{F'}{F}=coef\\ \ln(F)=\int coef\\ F=\exp\left(\int coef\right) \]

时间复杂度 \(O(n\log n)\)

CO int N=262144+10;
int fac[N],inv[N],ifac[N];

void NTT(poly&a,int dir){
    static int rev[N],omg[N];
    int lim=a.size(),len=log2(lim);
    for(int i=0;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
    for(int i=0;i<lim;++i)if(i<rev[i]) swap(a[i],a[rev[i]]);
    omg[0]=1,omg[1]=fpow(dir==1?3:332748118,(mod-1)/lim);
    for(int i=2;i<lim;++i) omg[i]=mul(omg[i-1],omg[1]);
    for(int i=1;i<lim;i<<=1)
        for(int j=0;j<lim;j+=i<<1)
            for(int k=0;k<i;++k){
                int t=mul(omg[lim/(i<<1)*k],a[j+i+k]);
                a[j+i+k]=add(a[j+k],mod-t),a[j+k]=add(a[j+k],t);
            }
    if(dir==-1){
        for(int i=0;i<lim;++i) a[i]=mul(a[i],inv[lim]);
    }
}
poly inver(poly a){
    int n=a.size();
    poly b(1,fpow(a[0],mod-2));
    if(n==1) return b;
    int lim=2;
    for(;lim<n;lim<<=1){
        poly a1(a.begin(),a.begin()+lim);
        a1.resize(lim<<1),NTT(a1,1);
        b.resize(lim<<1),NTT(b,1);
        for(int i=0;i<lim<<1;++i) b[i]=mul(add(2,mod-mul(a1[i],b[i])),b[i]);
        NTT(b,-1),b.resize(lim);
    }
    a.resize(lim<<1),NTT(a,1);
    b.resize(lim<<1),NTT(b,1);
    for(int i=0;i<lim<<1;++i) b[i]=mul(add(2,mod-mul(a[i],b[i])),b[i]);
    NTT(b,-1),b.resize(n);
    return b;
}
poly differ(CO poly&a){
    poly b(a.size()-1);
    for(int i=0;i<(int)b.size();++i) b[i]=mul(a[i+1],i+1);
    return b;
}
poly inter(CO poly&a){
    poly b(a.size()+1);
    for(int i=1;i<(int)b.size();++i) b[i]=mul(a[i-1],inv[i]);
    return b;
}
poly log(poly a){
    int n=a.size();
    poly b=inver(a);
    a=differ(a);
    int lim=1<<int(ceil(log2(2*n-2)));
    a.resize(lim),NTT(a,1);
    b.resize(lim),NTT(b,1);
    for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
    NTT(a,-1),a.resize(n);
    a=inter(a),a.resize(n);
    return a;
}
poly exp(poly a){
    int n=a.size();
    poly b(1,1);
    if(n==1) return b;
    int lim=2;
    for(;lim<n;lim<<=1){
        poly a1(a.begin(),a.begin()+lim);
        a1.resize(lim<<1),NTT(a1,1);
        b.resize(lim);poly b1=log(b);
        b1.resize(lim<<1),NTT(b1,1);
        b.resize(lim<<1),NTT(b,1);
        for(int i=0;i<lim<<1;++i) b[i]=mul(add(1,add(a1[i],mod-b1[i])),b[i]);
        NTT(b,-1),b.resize(lim);
    }
    a.resize(lim<<1),NTT(a,1);
    b.resize(lim);poly b1=log(b);
    b1.resize(lim<<1),NTT(b1,1);
    b.resize(lim<<1),NTT(b,1);
    for(int i=0;i<lim<<1;++i) b[i]=mul(add(1,add(a[i],mod-b1[i])),b[i]);
    NTT(b,-1),b.resize(n);
    return b;
}

namespace Subtask2{
    
    void main(int n,int y){
        fac[0]=1;
        for(int i=1;i<N;++i) fac[i]=mul(fac[i-1],i);
        inv[0]=inv[1]=1;
        for(int i=2;i<N;++i) inv[i]=mul(mod-mod/i,inv[mod%i]);
        ifac[0]=1;
        for(int i=1;i<N;++i) ifac[i]=mul(ifac[i-1],inv[i]);
        
        int weight=mul(add(fpow(y,mod-2),mod-1),fpow(n,2*mod-4));
        poly coef(n+1);
        for(int i=0;i<=n;++i) coef[i]=mul(fpow(i+1,i+1),mul(fpow(weight,i),ifac[i]));
        poly lnres=inter(coef);lnres.resize(n+1);
        poly res=exp(lnres);
        int ans=mul(res[n],fac[n]);
        ans=mul(ans,mul(fpow(y,n),fpow(n,2*n-4)));
        printf("%d\n",ans);
    }
}

生成树计数

在一个 \(s\) 个点的图中,存在 \(s-n\) 条边,使图中形成了 \(n\) 个连通块,第 \(i\) 个连通块中有 \(a_i\) 个点。

现在我们需要再连接 \(n-1\) 条边,使该图变成一棵树。对一种连边方案,设原图中第 \(i\) 个连通块连出了 \(d_i\) 条边,那么这棵树 \(T\) 的价值为:

\[ \mathrm{val}(T) = \left(\prod_{i=1}^{n} d_i^m\right)\left(\sum_{i=1}^{n} d_i^m\right) \]

你的任务是求出所有可能的生成树的价值之和,对 \(998244353\) 取模。

\(100\%\) 的数据中,\(n \le 3\times 10^4,m \le 30\)

斯特林做法

连加的性质显然比连乘多,所以我们把 \(\mathrm{val}(T)\) 定义式中的连加提到前面来。

\[ \mathrm{val}(T)=\sum_{i=1}^nd_i^m\prod_{j=1}^{n} d_j^m\\ =\sum_{i=1}^nd_i^{2m}\prod_{j\neq i} d_j^m \]

先用枚举树的形态列出暴力求和式

\[ ans=\sum_T\sum_{i=1}^nd_i^{2m}\prod_{j\neq i}d_j^m \]

答案只与度数有关,所以我们联想到Prufer序列。我们对连通块做Prufer序列,枚举每个连通块出现的次数 \(d_i\),那么连通块的连边数应为 \(d_i+1\)。同时由于此时的Prufer序列装的是连通块,所以我们还要对每个连通块乘以连边方案 \(a_i^{d_i+1}\)

\[ ans=\sum_D \frac{(n-2)!}{\prod d!}\prod a^{d+1}\sum_{i=1}^n (d_{i}+1)^{2m} \prod_{j\neq i} (d_j+1)^m\\ =\sum_D \frac{(n-2)!}{\prod d!} \sum_{i=1}^n a_i^{d_i+1}(d_{i}+1)^{2m} \prod_{j\neq i} a_j^{d_j+1}(d_j+1)^m\\ =(n-2)!\sum_{i=1}^n\sum_D \frac{a_i^{d_i+1}(d_{i}+1)^{2m}}{d_i!} \prod_{j\neq i} \frac{a_j^{d_j+1}(d_j+1)^m}{d_j!} \]

用生成函数优化 \(D\) 的枚举。

\[ A_i(x)=\sum_{k=0}^\infty \frac{a_i^{k+1}(k+1)^m}{k!}x^k\\ B_i(x)=\sum_{k=0}^\infty \frac{a_i^{k+1}(k+1)^{2m}}{k!}x^k\\ ans=(n-2)!\sum_{i=1}^n \left(B_i \prod_{j \neq i} A_j\right)[x^{n-2}] \]

接下来我们要寻找快速计算 \(B_i \prod_{j \neq i} A_j\) 的方法。注意到 \(A,B\) 的形式是指数型生成函数,而系数与指数没有对上,所以不妨对 \(A\) 积分。

\[ \int A_i(x)\mathrm dx=\sum_{k=1}^\infty a_i^kk^m\frac{x^k}{k!} \]

数据范围一开始就提示我们要用斯特林套路,现在这个式子想要发生变化的唯一可能性就是使用斯特林套路了。所以我们上斯特林套路。

\[ \int A_i(x)\mathrm dx=\sum_{k=1}^\infty a_i^k\frac{x^k}{k!}\sum_{j=0}^m\begin{Bmatrix}m\\j\end{Bmatrix}k^\underline{j}\\ =\sum_{j=0}^m\begin{Bmatrix}m\\j\end{Bmatrix}\sum_{k=j}^\infty a_i^kk^\underline{j}\frac{x^k}{k!}\\ =\sum_{j=0}^m\begin{Bmatrix}m\\j\end{Bmatrix}\sum_{k=j}^\infty a_i^k\frac{x^k}{(k-j)!} \]
注意到 \(\frac{1}{(k-j)!}\) 的形式,我们把后面那个求和式都提一个 \(k-j\) 出来。

\[ \int A_i(x)\mathrm dx=\sum_{j=0}^m\begin{Bmatrix}m\\j\end{Bmatrix}a_i^jx^j\sum_{k=0}^\infty a_i^k\frac{x^k}{k!}\\ =e^{a_ix}\sum_{j=0}^m\begin{Bmatrix}m\\j\end{Bmatrix}a_i^jx^j \]

\(B\)\(A\) 的唯一区别就是 \(k^m\) 变成了 \(k^{2m}\),所以 \(\int B\) 就简单变化一下斯特林套路枚举的范围即可。

\[ \int B_i(x)\mathrm dx=e^{a_ix}\sum_{j=0}^{2m}\begin{Bmatrix}2m\\j\end{Bmatrix}a_i^jx_j\\ \]

现在我们再来求导

\[ A_i(x)=e^{a_x}\sum_{j=0}^m\begin{Bmatrix}m\\j\end{Bmatrix}a_i^{j+1}x^j+e^{a_ix}\sum_{j=0}^m\begin{Bmatrix}m\\j+1\end{Bmatrix}a_i^{j+1}(j+1)x^j\\ =e^{a_ix}\sum_{j=0}^m\left(\begin{Bmatrix}m\\j\end{Bmatrix}+(j+1)\begin{Bmatrix}m\\j+1\end{Bmatrix}\right)a_i^{j+1}x^j\\ =e^{a_ix}\sum_{j=0}^m\begin{Bmatrix}m+1\\j+1\end{Bmatrix}a_i^{j+1}x^j\\ B_i(x)=e^{a_ix}\sum_{j=0}^{2m}\begin{Bmatrix}2m+1\\j+1\end{Bmatrix}a_i^{j+1}x^j \]

最后我们再来推 \(B_i\prod_{j\neq i}A_j\) 的式子。

\[ B_i(x)\prod_{j\neq i}A_i(x)=e^{\sum ax}\left(\sum_{k=0}^{2m}\begin{Bmatrix}2m+1\\k+1\end{Bmatrix}a_i^{k+1}x^k\right)\prod_{j\neq i}\left(\sum_{k=0}^m\begin{Bmatrix}m+1\\k+1\end{Bmatrix}a_j^{k+1}x^k\right) \]

注意到前面的 \(e^{\sum ax}\) 是个定值,而后面的可以分治NTT求,所以这题就做完了。时间复杂度 \(O(nm\log^2n)\)

如何分治NTT呢?注意到我们只需要对所有的 \(B_i\prod_{j \neq i} A_j\) 求和,所以可以像solve(l,mid)求出 \(\sum_{i=l}^{mid}B_i\prod_{j=l,j\neq i}^{mid}A_j\) 后让它乘以 \(\prod_{j=mid+1}^rA_j\)这样计算。

但是这个做法并不本质,很多步骤进行的解释很牵强。比如斯特林套路的运用也是莫名其妙,普通人也不可能一眼就看出来这样可以化简。

本质做法

https://loj.ac/article/1257

套路一

先不考虑后面那个 \(\sum\),那么可以表示为若干个 \(w(d)=d^m\) 的积。

用EGF分配Prufer序列的位置,若第 \(i\) 个块分到 \(d_i\) 个位置,那么其度数为 \(d_i+1\)。并且要为每个度数决定连接哪个点,即 \(a_i^{d_i+1}\)

用EGF分配位置……生成函数本质上是工具。

\[ ans=(\prod a)\sum_D\frac{(n-2)!}{\prod d!}\prod_{i=1}^n a_i^{d_i}w(d_i+1) \]

这里把公因子 \(\prod a\) 提到了前面。\(\frac{(n-2)!}{\prod d!}\) 是分配位置,用EGF搞掉。

那么块 \(i\) 的EGF是 \(F_i(x) = \sum_{k=0}^\infty\frac{x^k}{k!} a_i^k w(k+1)\)\(w\) 不要漏写+1)。

可以发现如果设 \(F(x) = \sum_{k=0}^\infty\frac{x^k}{k!} w(k+1)\),那么 \(F_i(x)=F(a_ix)\)

把每块的EGF乘起来得到

\[ ans = (\prod a)(n-2)!\left(\prod_{i=1}^n F(a_ix)\right)[x^{n-2}] \]

以上推理只要树权值满足 \(\prod w(d)\) 的形式即可进行。

出题人给的 \(d^m\) 本质上是关于 \(d\) 的函数。

套路二

考虑一个简单的子程序:给一个 \(t\) 次多项式 \(P\)\(n\) 个数 \(a_1\sim a_n\),求 \(\sum_{i=1}^n P(a_ix)\)

\(P=\sum_{k=0}^t p_kx^k\) 那么

\[ \sum_{i=1}^n P(a_ix)=\sum_{k=0}^t p_k\sum_{i=1}^n a_i^kx^k \]

先求 \(A\)\(0\sim t\) 次等指数幂和 \(s_A(k)=\sum_{i=1}^na_i^k\),然后与 \(P\) 的系数对位相乘即可。

等指数幂和用

\[ \sum_{i=1}^n\frac{1}{1-a_ix}=\frac{\sum_{i=1}^n\prod_{j\ne i}(1-a_jx)}{\prod_{i=1}^n(1-a_ix)} \]

求出,复杂度 \(O(n\log^2n+t\log t)\)

考虑 \(\sum_{i=1}^n\prod_{j\ne i}(1-a_jx)\)\(\prod_{i=1}^n(1-a_ix)\) 的关系。可以发现后者任意 \(i\) 次项在前者中恰好被算了 \(n-i\) 次。所以求前者的时候可以简单处理。
在本题的应用中,都有 \(t=n-2\),复杂度 \(O(n\log^2n)\)

Solution

套路一讲到我们要求 \(\prod_{i=1}^n F(a_ix)\),其中 \(F(x) = \sum_{k=0}^\infty\frac{x^k}{k!} w(k+1)\)

套上 \(\ln\) 将积转为和

\[ \prod_{i=1}^n F(a_ix)=\exp\left(\sum_{i=1}^n\ln F(a_ix)\right)\\ =\exp\left(\sum_{i=1}^n(\ln F)(a_ix)\right) \]

因此我们对 \(F\)\(\ln\),执行一遍套路二,再 \(\exp\) 回来即可。

现在考虑原题的 \(val(T) = (\prod_{i=1}^n d_i^m ) (\sum_{i=1}^n d_i^m)\),尝试表示成积的形式,发现它就是硬点了某个 \(i\) 不贡献 \(w(d_i)=d_i^m\) 而贡献 \(w'(d_i)=d_i^{2m}\)

出题人右半边求和的本质就是更改某个连通块贡献。

相应地再设 \(F'(x) = \sum\frac{x^k}{k!} w'(k+1)\),那么就要枚举 \(i\) 并把一个参与 \(\prod\)\(F(a_ix)\) 替换成 \(F'(a_ix)\)

\[ ans=(\prod a)(n-2)!\left(\prod_{i=1}^n F(a_ix)\right)\left(\sum_{i=1}^n\frac{F'(a_ix)}{F(a_ix)}\right)[x^{n-2}] \]

求出 \(G=\frac{F'}F\) 再执行一遍套路二即可。

综上,共需求1次等幂和(含1分治FFT、1求逆、1乘法)、1 \(\ln\)、1 \(\exp\)、1求逆、2乘法。复杂度 \(O(n\log^2n+n\log m)\),其中 \(\log m\) 是快速幂。

\(m\) 可以做1e9。提交记录 3e4 跑了不到 160ms,预计 2.5s 内可以做到 2.6e5 或 5.2e5。

CO int N=65536;
int omg[2][N],rev[N],inv[N];

void NTT(poly&a,int dir){
    int lim=a.size(),len=log2(lim);
    for(int i=0;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
    for(int i=0;i<lim;++i)if(i<rev[i]) swap(a[i],a[rev[i]]);
    for(int i=1;i<lim;i<<=1)
        for(int j=0;j<lim;j+=i<<1)for(int k=0;k<i;++k){
            int t=mul(omg[dir][N/(i<<1)*k],a[j+i+k]);
            a[j+i+k]=add(a[j+k],mod-t),a[j+k]=add(a[j+k],t);
        }
    if(dir==1){
        int ilim=fpow(lim,mod-2);
        for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
    }
}
poly operator~(poly a){
    int n=a.size();
    poly b(1,fpow(a[0],mod-2));
    if(n==1) return b;
    int lim=2;
    for(;lim<n;lim<<=1){
        poly a1(a.begin(),a.begin()+lim);
        a1.resize(lim<<1),NTT(a1,0);
        b.resize(lim<<1),NTT(b,0);
        for(int i=0;i<lim<<1;++i) b[i]=mul(2+mod-mul(a1[i],b[i]),b[i]);
        NTT(b,1),b.resize(lim);
    }
    a.resize(lim<<1),NTT(a,0);
    b.resize(lim<<1),NTT(b,0);
    for(int i=0;i<lim<<1;++i) b[i]=mul(2+mod-mul(a[i],b[i]),b[i]);
    NTT(b,1),b.resize(n);
    return b;
}
poly log(poly a){
    int n=a.size();
    poly b=~a;
    for(int i=0;i<n-1;++i) a[i]=mul(a[i+1],i+1);
    a.resize(n-1);
    int lim=1<<(int)ceil(log2(2*n-2));
    a.resize(lim),NTT(a,0);
    b.resize(lim),NTT(b,0);
    for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
    NTT(a,1),a.resize(n);
    for(int i=n-1;i>=1;--i) a[i]=mul(a[i-1],inv[i]);
    return a[0]=0,a;
}
poly exp(poly a){
    int n=a.size();
    poly b(1,1); // a[0]=0
    if(n==1) return b;
    int lim=2;
    for(;lim<n;lim<<=1){
        poly a1(a.begin(),a.begin()+lim);
        b.resize(lim);poly b1=log(b);
        a1.resize(lim<<1),NTT(a1,0);
        b.resize(lim<<1),NTT(b,0);
        b1.resize(lim<<1),NTT(b1,0);
        for(int i=0;i<lim<<1;++i) b[i]=mul(add(1+a1[i],mod-b1[i]),b[i]);
        NTT(b,1),b.resize(lim);
    }
    b.resize(lim);poly b1=log(b);
    a.resize(lim<<1),NTT(a,0);
    b.resize(lim<<1),NTT(b,0);
    b1.resize(lim<<1),NTT(b1,0);
    for(int i=0;i<lim<<1;++i) b[i]=mul(add(1+a[i],mod-b1[i]),b[i]);
    NTT(b,1),b.resize(n);
    return b;
}
poly operator*(poly a,poly b){
    int n=a.size()-1,m=b.size()-1;
    int lim=1<<(int)ceil(log2(n+m+1));
    a.resize(lim),NTT(a,0);
    b.resize(lim),NTT(b,0);
    for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
    NTT(a,1),a.resize(n+m+1);
    return a;
}

int a[N];

poly prod(int l,int r){
    if(l==r) return (poly){1,mod-a[l]};
    int mid=(l+r)>>1;
    return prod(l,mid)*prod(mid+1,r);
}

int main(){
    omg[0][0]=1,omg[0][1]=fpow(3,(mod-1)/N);
    omg[1][0]=1,omg[1][1]=fpow(omg[0][1],mod-2);
    for(int i=2;i<N;++i){
        omg[0][i]=mul(omg[0][i-1],omg[0][1]);
        omg[1][i]=mul(omg[1][i-1],omg[1][1]);
    }
    inv[0]=inv[1]=1;
    for(int i=2;i<N;++i) inv[i]=mul(mod-mod/i,inv[mod%i]);
    
    int n=read<int>(),m=read<int>();
    if(n==1){
        puts(m==0?"1":"0");
        return 0;
    }
    poly F(n-1),G(n-1);
    int ifac=1;
    for(int i=0;i<n-1;++i){
        int t=fpow(i+1,m);
        ifac=mul(ifac,inv[i]);
        F[i]=mul(t,ifac);
        G[i]=mul(t,F[i]);
    }
    G=G*~F,G.resize(n-1);
    F=log(F);
    
    int ans=fpow(ifac,mod-2);
    for(int i=1;i<=n;++i) read(a[i]),ans=mul(ans,a[i]);
    poly Q=prod(1,n),P(n-1);
    Q.resize(n-1);
    for(int i=0;i<n-1;++i) P[i]=mul(Q[i],n-i);
    P=P*~Q,P.resize(n-1);
    
    for(int i=0;i<n-1;++i){
        F[i]=mul(F[i],P[i]);
        G[i]=mul(G[i],P[i]);
    }
    ans=mul(ans,(exp(F)*G)[n-2]);
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/autoint/p/12157243.html
今日推荐