【luogu P5219】无聊的水题 I(prufer序列)(多项式)
无聊的水题 I
题目链接:luogu P5219
题目大意
给你 n 个点,然后问你有多少棵树满足它的最大度数的点是 m。
思路
看着这种构造树,我们有行列式的生成树计数,但这里感觉不是很好弄。
所以我们考虑用另一个东西:Prufer 序列。
Prufer 序列是每次选当前度数最小的点(随便一个),然后把它的父亲放进序列中,到剩下两个点的时候停止。
然后每个序列都对于一个不同的树,也就是说一共 \((n-2)!\) 棵树。
然后我们不难想到一个点的度数就是它在 Prufer 序列里面的出现次数加一。
那我们就可以把问题变成你有多少个长度为 \(n-2\) 的序列,然后你填 \(0\sim n\) 的数,每个数的最多次数不能超过 \(m-1\),而且至少有一个是 \(m-1\)。
那后面这个至少我们可以容斥掉,就是用最多次数不能超过 \(m-1\) 的减去最多不能超过 \(m-2\) 的。
那问题就是你有多少个长度为 \(n-2\) 的序列,然后你填 \(0\sim n\) 的数,每个数的最多次数不能超过 \(m-1\)。
考虑上 DP,设 \(f_{x,len}\) 为当前长度为 \(len\),已经用了 \(x\) 个数字有多少方案。
然后不难看出 \(O(n^3)\) 转移。
\(f_{x,len}=\sum\limits_{ i=0}^{min(len,m)}f_{x-1,len-i}*C(len,i)\)
然后分解 \(C\) 来化简:
\(f_{x,len}=\sum\limits_{ i=0}^{min(len,m)}f_{x-1,len-i}*\dfrac{len!}{i!(len-i)!}\)
\(\dfrac{f_{x,len}}{len!}=\sum\limits_{ i=0}^{min(len,m)}\dfrac{f_{x-1,len-i}}{i!(len-i)!}\)
\(\dfrac{f_{x,len}}{len!}=\sum\limits_{ i=0}^{min(len,m)}\dfrac{f_{x-1,len-i}}{(len-i)!}*\dfrac{1}{i!}\)
然后你会发现如果你设 \(F[x](len)=\dfrac{f_{x,len}}{len!}\),\(G(x)=\dfrac{1}{x!}\),那式子就变成了 \(F[x](len)=\sum\limits_{i=0}^{min(len,m)}F[x-1](len-i)G(i)\)
你发现每次都是一个卷积,而且每次乘的都是一样的。
那我们就可以直接用快速幂的形式,直接 \(O(n\log^2n)\),不过最后记得乘上 \(len!\) 才是答案。
好像也可以用那个什么指数多项式做到更优的 \(O(n\log n)\),但是我没学awa。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define mo 998244353
#define clr(f, x) memset(f, 0, sizeof(ll) * (x))
#define cpy(f, g, x) memcpy(f, g, sizeof(ll) * (x))
using namespace std;
const ll N = 50001;
ll n, m, jc[N << 2], inv[N << 2], x[N << 2];
ll GG[N << 2], an[N << 2], l_size, limit, rek[N << 2];
ll G = 3, Gv;
ll ksm(ll x, ll y) {
ll re = 1;
while (y) {
if (y & 1) re = re * x % mo;
x = x * x % mo;
y >>= 1;
}
return re;
}
ll inv_(ll x) {
return ksm(x, mo - 2);
}
void NTT(ll *f, ll limit, ll op) {
for (int i = 0; i < limit; i++)
if (i < an[i]) swap(f[i], f[an[i]]);
for (int mid = 1; mid < limit; mid <<= 1) {
ll Wn = ksm(op == 1 ? G : Gv, (mo - 1) / (mid << 1));
for (int R = (mid << 1), j = 0; j < limit; j += R) {
ll w = 1;
for (int k = 0; k < mid; k++, w = w * Wn % mo) {
ll x = f[j + k], y = w * f[j + mid + k] % mo;
f[j + k] = (x + y) % mo;
f[j + mid + k] = (x - y + mo) % mo;
}
}
}
if (op == -1) {
ll invl = inv_(limit);
for (int i = 0; i < limit; i++)
f[i] = f[i] * invl % mo;
}
}
void cheng(ll *f, ll *g) {
for (int i = 0; i < limit; i++)
f[i] = f[i] * g[i] % mo;
}
ll xl_ksm(ll *X, ll p, ll n, ll pl) {//直接就快速幂,把乘变成多项式的乘即可
cpy(x, X, p); clr(rek, p); rek[0] = 1;
limit = 1; l_size = 0;
while (limit <= p * 2) {
limit <<= 1; l_size++;
}
for (int i = 0; i < limit; i++) {
an[i] = (an[i >> 1] >> 1) | ((i & 1) << (l_size - 1));
}
while (n) {
if (n & 1) {
NTT(x, limit, 1); NTT(rek, limit, 1);
cheng(rek, x); NTT(x, limit, -1); NTT(rek, limit, -1);
clr(rek + p + 1, limit - p);
}
NTT(x, limit, 1);
cheng(x, x); NTT(x, limit, -1);
clr(x + p + 1, limit - p);
n >>= 1;
}
return rek[pl];
}
ll work(ll len, ll n, ll m) {
clr(GG, N << 2);
for (int i = 0; i <= m; i++) GG[i] = inv[i];
return xl_ksm(GG, len + 1, n, len) * jc[len] % mo;
}
int main() {
scanf("%lld %lld", &n, &m);
Gv = inv_(G);
jc[0] = 1; for (int i = 1; i <= N << 2; i++) jc[i] = jc[i - 1] * i % mo;
inv[N << 2] = inv_(jc[N << 2]);
for (int i = (N << 2) - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mo;
printf("%lld", (work(n - 2, n, m - 1) - work(n - 2, n, m - 2) + mo) % mo);
return 0;
}