卧薪尝胆,厚积薄发。
HZOI2015 有标号的DAG计数II
Date: Thu Nov 29 10:53:31 CST 2018 In Category: NoCategory

Description:

给定一正整数 $n$ ,对 $n$ 个点有标号的有向无环图(可以不连通)进行计数,输出答案 $mod 998244353$ 的结果。
$1\leqslant n\leqslant 10^5$

Solution:

考虑 $DP$ ,从入度为 $0$ 的点入手,设 $f(i,j)$ 表示 $i$ 个点中有 $j$ 个入度为 $0$ 的点,转移时枚举删掉这 $j$ 个点后的入度为 $0$ 的点的个数 $k$ : $$ f[i][j]=C_i^j\sum_{k=1}^{i-j}(2^j-1)^k2^{j(i-j-k)}f[i-j][k] $$ 这样得到了一个 $O(n^3)$ 的 $DP$ ,考虑优化它,发现状态定义太严格,不好转移。
考虑求解 $n$ 个点的方案数,然后再容斥: $$ f[n]=\sum_{k=1}^n(-1)^{k+1}C_n^k2^{k(n-k)}f[n-k] $$ 大概就是保证至少 $k$ 个点入度为 $1$ ,选出这些点有 $C_n^k$ 种方案,往后的连边情况是 $2^{k(n-k)}$ ,剩下的方案数为 $f[n-k]$ 。
这样我们就得到了一个 $O(n^2)$ 的 $DP$ ,发现这个和卷积的形式很相似,于是再化一下: $$ f[n]=\sum_{k=1}^n\bigl((-1)^{k+1}C^k_n\bigr)\times f[n-k]\times 2^{k(n-k)} $$ 于是我们现在唯一的问题就是 $2^{k(n-k)}$ 怎么处理,由于 $2^{(n-k)^2}=2^{n^2-2nk+k^2}$ ,所以: $$ 2^{k(n-k)}=2^{\frac{n^2-k^2-(n-k)^2}2}=\sqrt2^{n^2-k^2-(n-k)^2} $$
$$ f[n]=\Biggl(\sum_{k=1}^n\Bigl((-1)^{k+1}(k!)^{-1}(\sqrt2)^{-k^2}\Bigr)\times \Bigl(f[n-k]((n-k)!)^{-1}(\sqrt2^{-(n-k)^2})\Bigr)\Biggr)\times n!\sqrt2^{n^2} $$
注意由于 $2$ 与 $\varphi(P)=P-1$ 不互质,所以没有逆元,要求二次剩余,但是不用 $cipolla$ ,只要暴枚每个判断就行了。
这个式子怎么求?可以先移项: $$ \frac{f[n]}{n!\sqrt2^{n^2}}=\Biggl(\sum_{k=1}^n\Bigl((-1)^{k+1}(k!)^{-1}(\sqrt2)^{-k^2}\Bigr)\times \Bigl(\frac{f[n-k]}{(n-k)!\sqrt2^{(n-k)^2}}\Bigr)\Biggr) $$ 这样式子就好看了许多,于是设: $$ \begin{align} F[i]&=\frac{f[i]}{i!\sqrt2^{i^2}}\\ G[i]&=(-1)^{i+1}(i!)^{-1}(\sqrt2)^{-i^2}\\ \end{align} $$ 这样就有: $$ F[i]=\sum_{k=1}^iG[k]\times F[i-k] $$ 这样就是一个标准的分治 $FFT$ 的形式,可以用多项式求逆解决。

Code:


#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
int n;
#define MAXN 300010
#define P 998244353
#define REM2 116195171
typedef long long ll;
ll F[MAXN],G[MAXN];
ll fac[MAXN],inv[MAXN];
ll power(ll a,ll b)
{
ll res = 1;
while(b > 0)
{
if(b & 1)res = res * a % P;
a = a * a % P;
b = b >> 1;
}
return res;
}
ll C(ll n,ll m)
{
return fac[n] * inv[m] % P * inv[n - m] % P;
}
ll inver(ll k){return power(k,P - 2);}
ll ww[MAXN << 1],*g = ww + MAXN;
int rev[MAXN];
ll c[MAXN];
void NTT(ll f[],int l,int type)
{
for(int i = 0;i < l;++i)
{
if(i < rev[i])swap(f[i],f[rev[i]]);
}
for(int i = 2;i <= l;i = i << 1)
{
ll wn = g[type * l / i];
for(int j = 0;j < l;j += i)
{
ll w = 1;
for(int k = j;k < j + i / 2;++k)
{
ll u = f[k],t = w * f[k + i / 2] % P;
f[k] = (u + t) % P;
f[k + i / 2] = (u - t + P) % P;
w = w * wn % P;
}
}
}
if(type == -1)
{
ll invl = power(l,P - 2);
for(int i = 0;i < l;++i)f[i] = f[i] * invl % P;
}
return;
}
void calc_inv(int deg,ll a[],ll b[])
{
if(deg == 1)
{
b[0] = power(a[0],P - 2);
return;
}
calc_inv((deg + 1) >> 1,a,b);
int l = 0,len = 1;
for(;len < (deg << 1);len = len << 1)++l;
for(int i = 0;i < len;++i)rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
g[0] = g[-len] = 1;g[1] = g[1 - len] = power(3,(P - 1) / len);
for(int i = 2;i < len;++i)g[i] = g[i - len] = g[i - 1] * g[1] % P;
for(int i = 0;i < deg;++i)c[i] = a[i];
for(int i = deg;i < len;++i)c[i] = 0;
NTT(c,len,1);NTT(b,len,1);
for(int i = 0;i < len;++i)b[i] = (2 * b[i] % P - c[i] * b[i] % P * b[i] % P + P) % P;
NTT(b,len,-1);
for(int i = deg;i < len;++i)b[i] = 0;
return;
}
int main()
{
freopen("dag_count.in","r",stdin);
freopen("dag_count.out","w",stdout);
scanf("%d",&n);
fac[0] = 1;
for(int i = 1;i <= n;++i)fac[i] = fac[i - 1] * i % P;
inv[n] = power(fac[n],P - 2);
for(int i = n - 1;i >= 0;--i)inv[i] = inv[i + 1] * (i + 1) % P;
for(int i = 1;i <= n;++i)
{
G[i] = (((i & 1) ? 1 : -1) * inv[i] * power(inver(REM2),1ll * i * i) % P + P) % P;
}
for(int i = 1;i <= n;++i)G[i] = (P - G[i]) % P;
G[0] = 1;
calc_inv(n + 1,G,F);
F[n] = F[n] * fac[n] % P * power(REM2,1ll * n * n) % P;
cout << F[n] << endl;
fclose(stdin);
fclose(stdout);
return 0;
}
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡