题目简述

  • 有一个 \(n\) 个点的树,每次随机一个 \(1\)\(\binom{n}{2}\) 的排列作为边权,然后生成最小生成树,求最小生成树为一条链的概率。
  • \(n\le 200\)

题解

  • 嗯首先有想对于不同的链计数有什么区别,发现链的编号顺序是无所谓的,因为只需要对重编号,一种生成树都会变成另外的生成树,这样的话算一条链的概率成 \(n!\) 就行了,但是还要 \(\div 2\) 因为链的端点是可以互换的。
  • 那怎么对链计数呢?自然的我们考虑枚举链上的边权,从小到大,那么我们我们的限制就是在断开当前边,所产生的所有边中,链上的边边全最小。
  • 这里就是常见的非链上的边,顺序无所谓,合并到全局,当前链上的边一定最小,\(\binom{o+x}{x}\) \(o\) 是原来的边数,\(x\) 是新加非链上边。
  • 然后后枚举所有短边方法就行。枚举排列可做 \(n\le 10\).
  • 注意到只有每一段剩下的联通块大小是重要的,我们可以借助划分数 \(dp\),可做到 \(n\le 35\)
  • 嗯,这种思路极限就到这里了,之所以继续不了了,是因为我们在算 \(x\) 时需要用到全局的贡献,那就不能细化问题。那么怎么不用到全局问题呢?
  • 不妨考虑化简为烦,我们明明能直接确定所有的点,但我们偏不,我们依次从小到大枚举边权,然后判定这点边是链上的还是非链上的。
  • 其实就是我们把从权小的开始模拟断边,变成模拟加边,每次加边相当于合并两个联通块,联通块之间的所有边的权都应该大于连接两个联通块的链上的边,故此我们设 \(dp_{x,k}\) 表示当前联通块大小为 \(x\),通过权值枚举到地 \(k\) 条边。
  • 若加入一条链上的边,\(dp_{x,k}=\sum_{y,t}dp_{y,t}dp_{x-y,k-1-t}\binom{k-1-t}{t}\) 就可以了转移其实很简单。
  • 若加入一条非链上的边,\(dp_{x,k}=(\binom{x}{2}-(k-1))dp_{x,k-1}\) 所有没选的边等概率的选
  • 由于算得方案数,最后除 \(m!\) 就行了。
  • 这里我们已经可以做到 \(O(n^6)\) 瓶颈在于加一条链的转移,发现其很很想卷积,我们尝试卷掉一个 \(m^2\to mlog\),或者 \(n^2\to nlog\) 显然我们选择前者,直接做的化复杂度变为 \(O(n^4log+n^3)\)
  • 额,但是 \(ntt\) 在卷积的时候有性质,我才知道,\(dp\) 的值由于需要枚举 \(x,y\) 所以,相当于卷积后加在一起,我们可以先做半次 \(ntt\) 然后,算玩 \(a_i\times b_i\) 那步骤后不急着还原,累加完再统一还原一次就行了,这样的化复杂度变为 \(O(n^4+n^3log)\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 44000;
struct mmod
{
    ll m, p;
    void init(int pp) { m = ((__int128)1 << 64) / pp; p = pp; }
    ll operator ()(ll x)
    {
        return x - ((__int128(x) * m) >> 64) * p;
    }
} Mod;
ll n, mod;
ll A[N], V[N];
ll ksm(ll a, ll b)
{
    ll ans = 1;
    while (b)
    {
        if (b & 1)ans = ans * a % mod;
        b >>= 1; a = a * a % mod;
    }
    return ans;
}
ll dp[220][N], g[220][N];
ll C(ll n, ll m) { return A[n] * V[m] % mod * V[n - m] % mod; }
ll C2(ll n) { return n * (n - 1) >> 1; }
ll a[N], b[N], c[N]; int rev[N];
ll G, Gi;
ll get_g()
{
    ll phi = mod - 1;
    for (ll g = 2;;g++)
    {
        bool f = true;
        for (ll i = 2;i * i <= phi;i++)
            if (phi % i == 0 && ksm(g, phi / i) == 1) { f = false;break; }
        if (f)return g;
    }
}
void ntt(ll* a, int nn, int v)
{
    for (int i = 0;i < nn;i++)if (i < rev[i])swap(a[i], a[rev[i]]);
    for (int m = 1;m < nn;m <<= 1)
    {
        ll w1 = ksm(v == 1 ? G : Gi, (mod - 1) / m / 2);
        for (int i = 0;i < nn;i += m * 2)
        {
            ll wn = 1;
            for (int j = 0;j < m;j++, wn = wn * w1 % mod)
            {
                const ll x = a[i + j], y = a[i + j + m] * wn;
                a[i + j] = Mod(x + y);
                a[i + j + m] = Mod(x - y + mod);
            }
        }
    }
}
void ntt(int n)
{
    for (int i = 0;i <= n;i++)
        for (int j = 0;j <= i;j++)
            c[i] = (c[i] + a[j] * b[i - j]) % mod;
}
__int128 tmp[N];
int nn = 1, bit;
void solve(int n)
{
    while (nn < C2(n))nn <<= 1, bit++;
    for (int j = 0;j < nn;j++)rev[j] = (rev[j >> 1] >> 1) | ((j & 1) << bit - 1);
    dp[1][0] = 1;g[1][0] = 1; ntt(g[1], nn, 1);
    const ll inv = ksm(nn, mod - 2);
    for (int i = 0;i < C2(n);i++)A[i] = A[i] * inv % mod;
    for (int i = 2;i <= n;i++)
    {
        for (int j = 0;j < nn;j++)tmp[j] = 0;
        for (int k = 1;k < i;k++)for (int j = 0;j < nn;j++)
            tmp[j] += __int128(g[k][j]) * g[i - k][j];
        for (int j = 1;j <= nn;j++)dp[i][j] = tmp[j - 1] % mod;
        ntt(dp[i] + 1, nn, 0);
        for (int j = i - 1;j <= C2(i);j++)
            dp[i][j] = Mod(dp[i][j] * A[j - 1]);
        for (int j = i - 1;j <= C2(i);j++)
            dp[i][j] = Mod(dp[i][j] + dp[i][j - 1] * (C2(i) - (j - 1)));
        for (int j = i - 1;j <= C2(i);j++)g[i][j] = Mod(dp[i][j] * V[j]);
        ntt(g[i], nn, 1);
    }
}
int main()
{
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    A[0] = 1; cin >> n >> mod;Mod.init(mod); G = get_g();Gi = ksm(G, mod - 2);
    for (ll i = 1; i < N; i++)A[i] = A[i - 1] * i % mod;
    V[N - 1] = ksm(A[N - 1], mod - 2);
    for (ll i = N - 1; i >= 1; i--)V[i - 1] = V[i] * i % mod;
    solve(n);cout << 1 << '\n';
    for (int i = 2;i <= n;i++)
    {
        cout << (dp[i][C2(i)] * V[C2(i)] % mod * A[i] % mod * nn % mod * ksm(2, mod - 2) % mod + mod) % mod << '\n';
    }
    return 0;
}

posted @ 2025-02-14 07:27  LUHCUH  阅读(47)  评论(1)    收藏  举报