【luogu P5219】无聊的水题 I(prufer序列)(多项式)

无聊的水题 I

题目链接:luogu P5219

题目大意

给你 n 个点,然后问你有多少棵树满足它的最大度数的点是 m。

思路

看着这种构造树,我们有行列式的生成树计数,但这里感觉不是很好弄。
所以我们考虑用另一个东西:Prufer 序列。

Prufer 序列是每次选当前度数最小的点(随便一个),然后把它的父亲放进序列中,到剩下两个点的时候停止。
然后每个序列都对于一个不同的树,也就是说一共 (n2)! 棵树。

然后我们不难想到一个点的度数就是它在 Prufer 序列里面的出现次数加一。

那我们就可以把问题变成你有多少个长度为 n2 的序列,然后你填 0n 的数,每个数的最多次数不能超过 m1,而且至少有一个是 m1
那后面这个至少我们可以容斥掉,就是用最多次数不能超过 m1 的减去最多不能超过 m2 的。

那问题就是你有多少个长度为 n2 的序列,然后你填 0n 的数,每个数的最多次数不能超过 m1
考虑上 DP,设 fx,len 为当前长度为 len,已经用了 x 个数字有多少方案。

然后不难看出 O(n3) 转移。
fx,len=i=0min(len,m)fx1,leniC(len,i)

然后分解 C 来化简:
fx,len=i=0min(len,m)fx1,lenilen!i!(leni)!
fx,lenlen!=i=0min(len,m)fx1,lenii!(leni)!
fx,lenlen!=i=0min(len,m)fx1,leni(leni)!1i!

然后你会发现如果你设 F[x](len)=fx,lenlen!G(x)=1x!,那式子就变成了 F[x](len)=i=0min(len,m)F[x1](leni)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__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P5219.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示