LOJ #3102. 「JSOI2019」神经网络

题目链接

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\)

\[F(x)=\sum_{n\geq 0}f(root,n,0/1/2)\cdot n!\sum_{k=1}^n\binom{n-1}{k-1}(-1)^{n-k}\frac{x^k}{k!} \]

即枚举剖分的段数,然后枚举实际段数容斥,\(n\) 段拼成 \(k\) 段的方案数为 \(\displaystyle \binom{n-1}{k-1}\),容斥部分方案数实际是按 \(k\) 段算的,所以是 \(\displaystyle\frac{x^k}{k!}\) 的系数。

于是最终的方案数大体上即所有 \(F(x)\) 乘积后的系数和,不过旋转同构的哈密顿回路是相同的,这里强制第一棵树的段必须在开头,并且节点 \(1\) 处于这一段中,结尾的段不属于第一棵树。于是把第一棵树拉出来特殊考虑:

\[F_1(x)=\sum_{n\geq 0}f(root,n,0/1/2)\cdot(n-1)!\sum_{k=1}^n\binom{n-1}{k-1}(-1)^{n-k}x^k \]

最后的答案就是 \(\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;
}
posted @ 2022-04-04 09:11  Neal_lee  阅读(41)  评论(0编辑  收藏  举报