题目简述
- 有一个 \(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;
}