P5333 [JSOI2019]神经网络
P5333 [JSOI2019]神经网络
Solution
EGF
表示有标号排列。
对每棵树分别算出划分成 \(i\) 条链的方案数,记为 \(f_i\)。
具体地:设 \(dp[u][i][0/1/2]\) 表示在 \(u\) 子树内拆分成 \(i\) 条已结束的链,
\(0\): 已拼完,无法再延伸
\(1\): 单点,可继续向上扩展
\(2\): 长度 \(>1\) 的链,且可继续向上扩展 (为了处理正反向方案数 \(\times 2\) 的问题)
对于根节点视作它可以向父节点扩展,即和其它节点同等处理
状态由划分的客观形态决定 不能对 \(1/2\) 情况人为钦定为 \(0\)
\(f_i = dp[root][i][0] + dp[root][i - 1][1] + 2 \times dp[root][i - 1][2]\)
\(dp\) 转移有点繁复,但写出来极度舒适
把所有链拼成一个路径排列,则相邻的链颜色不同。
对于其他树,EGF
为:(采用容斥思想,枚举相邻位置个数)
\[\sum\limits_{i = 1}^{k}f_i\times i! \times \sum\limits_{j = 0}^{i - 1}(-1)^{j}\binom{i - 1}{j}\frac{x^{i - j}}{(i - j)!}
\]
对于第一棵树,第一条链被固定为起点,且第一棵树最后一条链不能与第一条链相邻。
第一条链被固定为起点:但统计一号点不必作为排列的起点,只需平移即可得到合法哈密顿回路。
第一棵树最后一条链不能与第一条链相邻:可以把两条链拼起来看成第一棵树的另一种拆分方案,因此存在计重问题。
在前一个限制下,用任意合法方案减去强行钦定首尾同色的合法方案,得到第一棵树的 EGF
:
\[\sum\limits_{i = 1}^{k}f_i\times (i - 1)! \times \sum\limits_{j = 0}^{i - 1}(-1)^{j}\binom{i - 1}{j}\frac{x^{i - j - 1}}{(i - j - 1)!} - \sum\limits_{i = 1}^{k}f_i\times (i - 1)! \times \sum\limits_{j = 0}^{i - 1}(-1)^{j}\binom{i - 1}{j}\frac{x^{i - j - 2}}{(i - j - 2)!}
\]
注意后者最后一条链的选取有 \(i - 1\) 种方案,因此其前面的系数仍为 \((i - 1)!\) 而非 \((i - 2)!\)。
暴力卷积,最后把每一项的系数乘对应的阶乘(化成 EGF
的形式)。
#include<bits/stdc++.h>
#define LL long long
#define DB double
#define MOD 998244353
#define ls(x) x << 1
#define rs(x) x << 1 | 1
#define lowbit(x) x & (-x)
#define PII pair<int, int>
#define MP make_pair
#define VI vector<int>
#define VII vector<int>::iterator
#define EB emplace_back
#define SI set<int>
#define SII set<int>::iterator
#define QI queue<int>
using namespace std;
template<typename T> void chkmn(T &a, const T &b) { (a > b) && (a = b); }
template<typename T> void chkmx(T &a, const T &b) { (a < b) && (a = b); }
int inc(const int &a, const int &b) { return a + b >= MOD ? a + b - MOD : a + b; }
int dec(const int &a, const int &b) { return a - b < 0 ? a - b + MOD : a - b; }
int mul(const int &a, const int &b) { return 1LL * a * b % MOD; }
void Inc(int &a, const int &b) { ((a += b) >= MOD) && (a -= MOD); }
void Dec(int &a, const int &b) { ((a -= b) < 0) && (a += MOD); }
void Mul(int &a, const int &b) { a = 1LL * a * b % MOD; }
void Sqr(int &a) { a = 1LL * a * a % MOD; }
int qwqmi(int x, int k = MOD - 2)
{
int res = 1;
while(k)
{
if(k & 1) Mul(res, x);
Sqr(x), k >>= 1;
}
return res;
}
const int N = 5e3 + 5;
int fac[N], facinv[N];
void preprocess()
{
fac[0] = facinv[0] = 1;
for(int i = 1; i < N; ++i)
fac[i] = mul(fac[i - 1], i);
facinv[N - 1] = qwqmi(fac[N - 1]);
for(int i = N - 2; i >= 1; --i)
facinv[i] = mul(facinv[i + 1], i + 1);
}
int binom(int n, int m)
{
return mul(fac[n], mul(facinv[m], facinv[n - m]));
}
int m, n, sum;
LL f[N]; // f[i]: 把当前树拆成 i 条链的方案数(考虑排列在路径上的正反顺序)
struct Lexington
{
int e, ne;
};
struct Sakawa
{
int idx, h[N];
Lexington lxt[2 * N];
int sz[N], dp[N][N][3], tmp[N][3];
// dp[u][i] : 考虑将 u 子树划分为 i 条已完结的链. 0/1/2 表示 u 所在的链处于什么状态
// 0 : 已拼完,无法再延伸
// 1 : 单点,可继续向上扩展
// 2 : >1 的链,且可继续向上扩展 (为了处理正反向方案数 × 2 的问题)
// 对于根节点视作它可以向父节点扩展,即和其它节点同等处理
// 状态由划分的客观形态决定 不能对 1/2 情况人为钦定为 0
// f[i] = dp[root][i][0] + dp[root][i - 1][1] + 2 * dp[root][i - 1][2]
void add(int a, int b)
{
lxt[++idx] = (Lexington){b, h[a]};
h[a] = idx;
}
void adds(int a, int b)
{
add(a, b), add(b, a);
}
void clear()
{
idx = 0;
for(int i = 1; i <= n; ++i)
{
h[i] = 0;
for(int j = 0; j <= sz[i]; ++j)
{
dp[i][j][0] = 0;
dp[i][j][1] = 0;
dp[i][j][2] = 0;
}
}
}
void DPtrans(int u, int v)
{
for(int i = 0; i <= sz[u]; ++i)
for(int j = 0; j <= sz[v]; ++j)
{
Inc(tmp[i + j][0], mul(dp[u][i][0], dp[v][j][0]));
Inc(tmp[i + j][1], mul(dp[u][i][1], dp[v][j][0]));
Inc(tmp[i + j][2], mul(dp[u][i][1], dp[v][j][1]));
Inc(tmp[i + j][2], mul(dp[u][i][1], dp[v][j][2]));
Inc(tmp[i + j][2], mul(dp[u][i][2], dp[v][j][0]));
Inc(tmp[i + j + 1][0], mul(dp[u][i][0], dp[v][j][1]));
Inc(tmp[i + j + 1][0], mul(2, mul(dp[u][i][0], dp[v][j][2])));
Inc(tmp[i + j + 1][0], mul(2, mul(dp[u][i][2], dp[v][j][1])));
Inc(tmp[i + j + 1][0], mul(2, mul(dp[u][i][2], dp[v][j][2])));
Inc(tmp[i + j + 1][1], mul(dp[u][i][1], dp[v][j][1]));
Inc(tmp[i + j + 1][1], mul(2, mul(dp[u][i][1], dp[v][j][2])));
Inc(tmp[i + j + 1][2], mul(dp[u][i][2], dp[v][j][1]));
Inc(tmp[i + j + 1][2], mul(2, mul(dp[u][i][2], dp[v][j][2])));
}
sz[u] += sz[v];
for(int i = 0; i <= sz[u]; ++i)
{
dp[u][i][0] = tmp[i][0], tmp[i][0] = 0;
dp[u][i][1] = tmp[i][1], tmp[i][1] = 0;
dp[u][i][2] = tmp[i][2], tmp[i][2] = 0;
}
}
void dfs(int u, int fa)
{
sz[u] = 1;
dp[u][0][1] = 1;
for(int i = h[u]; i; i = lxt[i].ne)
{
int v = lxt[i].e;
if(v == fa) continue;
dfs(v, u);
DPtrans(u, v);
}
}
}skw;
int F[N], G[N], H[N], ans;
int main()
{
preprocess();
scanf("%d", &m);
F[0] = 1;
for(int cas = 1; cas <= m; ++cas)
{
scanf("%d", &n);
for(int i = 1; i < n; ++i)
{
int a, b;
scanf("%d %d", &a, &b);
skw.adds(a, b);
}
skw.dfs(1, 0);
for(int i = 1; i <= n; ++i)
f[i] = inc(inc(skw.dp[1][i][0], skw.dp[1][i - 1][1]), mul(2, skw.dp[1][i - 1][2]));
for(int i = 0; i <= n; ++i)
G[i] = 0;
if(cas == 1)
{
for(int i = 1; i <= n; ++i)
{
int c = mul(f[i], fac[i - 1]);
for(int j = 0; j < i; ++j)
{
int w = mul(c, mul(binom(i - 1, j), facinv[i - j - 1]));
if(j & 1) Dec(G[i - j - 1], w);
else Inc(G[i - j - 1], w);
}
}
for(int i = 1; i <= n; ++i)
{
int c = mul(f[i], fac[i - 1]);
for(int j = 0; j < i - 1; ++j)
{
int w = mul(c, mul(binom(i - 1, j), facinv[i - j - 2]));
if(j & 1) Inc(G[i - j - 2], w);
else Dec(G[i - j - 2], w);
}
}
}
else
{
for(int i = 1; i <= n; ++i)
{
int c = mul(f[i], fac[i]);
for(int j = 0; j < i; ++j)
{
int w = mul(c, mul(binom(i - 1, j), facinv[i - j]));
if(j & 1) Dec(G[i - j], w);
else Inc(G[i - j], w);
}
}
}
for(int i = 0; i <= sum + n; ++i)
H[i] = 0;
for(int i = 0; i <= sum; ++i)
for(int j = 0; j <= n; ++j)
Inc(H[i + j], mul(F[i], G[j]));
sum += n;
for(int i = 0; i <= sum; ++i)
F[i] = H[i];
skw.clear();
}
for(int i = 0; i <= sum; ++i)
Inc(ans, mul(F[i], fac[i]));
printf("%d\n", ans);
return 0;
}