LOJ #3102. 「JSOI2019」神经网络
题目链接
题目大意
给定一个包含 \(m\) 棵树的森林,然后在不同的树的节点之间两两连边,即两点之间不存在边当且仅当两者属于同一棵树且在树上没有连边。
求哈密顿回路数量,两回路不同当且仅当不旋转同构。
\(m\leq 300\),图的节点数 \(n\leq 5000\)
思路
不同树间的节点是可以任意到达的,因此哈密顿回路相当于将每棵树剖成若干条路径,然后路径头尾相连。
剖分路径的部分可以树上背包,\(f(i,j,0/1/2)\):子树 \(i\),有 \(j\) 条链,\(0\):\(i\) 连接了两条链,\(1\):\(i\) 位于一条链的顶部,\(2\):\(i\) 单独一个点,方案数。然后分类讨论转移便可以做到 \(O(n^2)\) 。
拼合路径要求来自同一棵树的两条路径不得相邻,有这个限制便需要容斥,这使得朴素合并比较麻烦。不过多重组合数形式的合并与 \(EGF\) 乘法是类似的,于是考虑一棵树的带权(容斥后)贡献的 \(EGF\):
即枚举剖分的段数,然后枚举实际段数容斥,\(n\) 段拼成 \(k\) 段的方案数为 \(\displaystyle \binom{n-1}{k-1}\),容斥部分方案数实际是按 \(k\) 段算的,所以是 \(\displaystyle\frac{x^k}{k!}\) 的系数。
于是最终的方案数大体上即所有 \(F(x)\) 乘积后的系数和,不过旋转同构的哈密顿回路是相同的,这里强制第一棵树的段必须在开头,并且节点 \(1\) 处于这一段中,结尾的段不属于第一棵树。于是把第一棵树拉出来特殊考虑:
最后的答案就是 \(\displaystyle \sum_{i\geq 0}\sum_{j\geq 0} [\frac{x^i}{i!}]\prod F(x)[x^j]F_1(x)\binom{i+j-2}{j-1}\) 。
时间复杂度 \(O(n^2)\) 。
Code
#include<iostream>
#include<cstring>
#include<vector>
#define mem(a,b) memset(a, b, sizeof(a))
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 5050
#define M 330
#define ll long long
#define mod 998244353
#define inv2 499122177
using namespace std;
struct Tree{
int n;
int head[N], nxt[2*N], to[2*N];
int cnt, siz[N];
vector<vector<vector<ll> > > f;
ll g[N][3];
void init(){
mem(head, -1), cnt = -1;
f.resize(n+2, vector<vector<ll> > (n+2, vector<ll>(3)));
}
void add_e(int a, int b, bool id){
nxt[++cnt] = head[a], head[a] = cnt, to[cnt] = b;
if(id) add_e(b, a, 0);
}
void dfs(int x, int fa){
siz[x] = 1, f[x][1][2] = 1;
for(int i = head[x]; ~i; i = nxt[i]) if(to[i] != fa){
int y = to[i];
dfs(y, x);
rep(i,1,siz[x]) rep(j,1,siz[y]){
ll tot = (f[y][j][0] + f[y][j][1] + f[y][j][2]) % mod;
(g[i+j-1][0] += f[x][i][1] * (f[y][j][1] * inv2 % mod + f[y][j][2])) %= mod;
(g[i+j][0] += f[x][i][0] * tot) %= mod;
(g[i+j-1][1] += f[x][i][2] * (2 * f[y][j][2] + f[y][j][1])) %= mod;
(g[i+j][1] += f[x][i][1] * tot) %= mod;
(g[i+j][2] += f[x][i][2] * tot) %= mod;
}
siz[x] += siz[y];
rep(i,1,siz[x]) rep(p,0,2)
f[x][i][p] = g[i][p], g[i][p] = 0;
}
}
} T[M];
int m, n;
ll f[M][N], g[M][N], F[N];
ll fact[N], invf[N];
ll qpow(ll a, int b){
ll ret = 1;
for(; b; b >>= 1){ if(b&1) (ret *= a) %= mod; (a *= a) %= mod; }
return ret;
}
void prework(){
fact[0] = 1;
rep(i,1,N-1) fact[i] = fact[i-1] * i % mod;
invf[N-1] = qpow(fact[N-1], mod-2);
per(i,N-2,0) invf[i] = invf[i+1] * (i+1) % mod;
}
ll C(int n, int m){
if(n < 0 || m < 0) return 0;
return fact[n] * invf[m] % mod * invf[n-m] % mod;
}
int main(){
cin>>m;
rep(i,1,m){
cin>>T[i].n, n += T[i].n;
int u, v; T[i].init();
rep(i_,1,T[i].n-1) cin>>u>>v, T[i].add_e(u, v, 1);
}
prework();
rep(i,1,m){
T[i].dfs(1, 0);
rep(j,1,T[i].n){
rep(p,0,2) (f[i][j] += T[i].f[1][j][p]) %= mod;
rep(k,1,j) (g[i][k] += f[i][j] * fact[j - (i==1)] % mod * C(j-1, k-1) % mod * ((j-k)&1 ? mod-1 : 1)) %= mod;
}
if(i > 1) rep(k,1,T[i].n)
(g[i][k] *= invf[k]) %= mod;
}
F[0] = 1;
rep(i,2,m) per(a,n,0) if(F[a]){
rep(b,1,T[i].n) (F[a+b] += F[a] * g[i][b]) %= mod;
F[a] = 0;
}
ll ans = 0;
rep(i,1,n) rep(j,1,T[1].n)
(ans += F[i] * fact[i] % mod * g[1][j] % mod * C(i+j-2, j-1)) %= mod;
cout<< ans <<endl;
return 0;
}