无聊的水题 I
题目大意
给你 n 个点,然后问你有多少棵树满足它的最大度数的点是 m。
思路
看着这种构造树,我们有行列式的生成树计数,但这里感觉不是很好弄。
所以我们考虑用另一个东西:Prufer 序列。
Prufer 序列是每次选当前度数最小的点(随便一个),然后把它的父亲放进序列中,到剩下两个点的时候停止。
然后每个序列都对于一个不同的树,也就是说一共 (n−2)! 棵树。
然后我们不难想到一个点的度数就是它在 Prufer 序列里面的出现次数加一。
那我们就可以把问题变成你有多少个长度为 n−2 的序列,然后你填 0∼n 的数,每个数的最多次数不能超过 m−1,而且至少有一个是 m−1。
那后面这个至少我们可以容斥掉,就是用最多次数不能超过 m−1 的减去最多不能超过 m−2 的。
那问题就是你有多少个长度为 n−2 的序列,然后你填 0∼n 的数,每个数的最多次数不能超过 m−1。
考虑上 DP,设 fx,len 为当前长度为 len,已经用了 x 个数字有多少方案。
然后不难看出 O(n3) 转移。
fx,len=min(len,m)∑i=0fx−1,len−i∗C(len,i)
然后分解 C 来化简:
fx,len=min(len,m)∑i=0fx−1,len−i∗len!i!(len−i)!
fx,lenlen!=min(len,m)∑i=0fx−1,len−ii!(len−i)!
fx,lenlen!=min(len,m)∑i=0fx−1,len−i(len−i)!∗1i!
然后你会发现如果你设 F[x](len)=fx,lenlen!,G(x)=1x!,那式子就变成了 F[x](len)=min(len,m)∑i=0F[x−1](len−i)G(i)
你发现每次都是一个卷积,而且每次乘的都是一样的。
那我们就可以直接用快速幂的形式,直接 O(nlog2n),不过最后记得乘上 len! 才是答案。
好像也可以用那个什么指数多项式做到更优的 O(nlogn),但是我没学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;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现