【luogu P4233】射命丸文的笔记(NTT)(多项式求逆)

射命丸文的笔记

题目链接:luogu P4233

题目大意

问你有每个 n 个点的有哈密顿回路的竞赛图的期望哈密顿回路数。
其中哈密顿回路就是从一个点出发不重复的经过每个点最终回到自己的路径。
竞赛图就是任意两点之间都有一条有向边。

思路

考虑求出所有图的哈密顿回路总数和有多少个图有哈密顿回路。

前者比较好求,你就选一套路径 n!n=(n1)!,然后其它边就可以随意了 2(n(n1)2n)

那接着问题就是后者,考虑一个容斥 DP:
fn=2n(n1)2i=1n1fi(ni)2(ni)(ni1)2
这里稍微解释一下,你每次就枚举一个块和其他的部分不能连通,那其他的部分是要连通的所以是 fi(这里枚举的是其他的部分),然后这个块里面随便选所以是 2(ni)(ni1)2,然后它们这两个部分之间肯定是只有从一边连向另一边,不用乘二是因为乘二到时就会有重复计算。

然后你考虑优化:
发现在 i=n 的时候 (ni)2(ni)(ni1)2 这个东西是 1,那我们完全可以把右边的减移到左边,从而变成这个:
i=1nfi(ni)2(ni)(ni1)2=2n(n1)2
然后我们拆开组合数:
i=1nfin!i!(ni)!2(ni)(ni1)2=2n(n1)2
移项:
i=1nfii!2(ni)(ni1)2(ni)!=2n(n1)2n!

然后发现我们设 F=ifii!xi,G=i2i(i1)2i!xi,然后就变成了这个:
G(x)=F(x)G(x)+1(这个 +1 是因为上面的 1n 的时候是没有第 0 项,要加回来)
然后就有 F(x)=G(x)1G(x),然后用多项式求逆即可。

代码

#include<cstdio> #include<cstring> #include<algorithm> #define ll long long #define mo 998244353 #define clear(f, n) memset(f, 0, (n) * sizeof(ll)) #define cpy(f, g, n) memcpy(f, g, (n) * sizeof(ll)) using namespace std; int n, an[800001], limit, l_size; ll f[800001], G, Gv, g[800001]; ll w[800001], r[800001], tmp[800001]; ll jc[800001], inv[800001]; 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; } void get_an() { for (int i = 0; i < limit; i++) an[i] = (an[i >> 1] >> 1) | ((i & 1) << (l_size - 1)); } void NTT(ll *f, ll op) { get_an(); 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 liv = ksm(limit, mo - 2); for (int i = 0; i < limit; i++) f[i] = f[i] * liv % mo; } } void px(ll *x, ll *y) { for (int i = 0; i < limit; i++) x[i] = x[i] * y[i] % mo; } void cheng(ll *x, int n, ll *y, int m) { limit = 1; l_size = 0; while (limit < n + m + 1) { limit <<= 1; l_size++; } NTT(x, 1); NTT(y, 1); px(x, y); NTT(x, -1); } void invp(ll *F, int n) { w[0] = ksm(F[0], mo - 2); l_size = 0; for (int len = 2; (len >> 1) <= n; len <<= 1) { limit = len; l_size++; for (int i = 0; i < (len >> 1); i++) r[i] = w[i]; cpy(tmp, F, len); NTT(tmp, 1); NTT(r, 1); px(r, tmp); NTT(r, -1); clear(r, (len >> 1)); cpy(tmp, w, len); NTT(tmp, 1); NTT(r, 1); px(r, tmp); NTT(r, -1); for (int i = len >> 1; i < len; i++) w[i] = (w[i] * 2 - r[i] + mo) % mo; } cpy(F, w, n); clear(tmp, n); clear(w, n); clear(r, n); } int main() { G = 3; Gv = ksm(G, mo - 2); scanf("%d", &n); jc[0] = 1; for (int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % mo; inv[0] = inv[1] = 1; for (int i = 2; i <= n; i++) inv[i] = inv[mo % i] * (mo - mo / i) % mo; for (int i = 1; i <= n; i++) inv[i] = inv[i - 1] * inv[i] % mo; for (int i = 0; i <= n; i++) { f[i] = ksm(2, 1ll * i * (i - 1) / 2) * inv[i] % mo; } cpy(g, f, n + 1); g[0] = (g[0] - 1 + mo) % mo; invp(f, n + 1); cheng(f, n + 1, g, n + 1); for (int i = 1; i <= n; i++) { if (i == 1) { printf("1\n"); continue; } if (i == 2) { printf("-1\n"); continue; } f[i] = f[i] * jc[i] % mo; ll all = ksm(2, 1ll * i * (i - 1) / 2 - i) * jc[i - 1] % mo; printf("%lld\n", all * ksm(f[i], mo - 2) % mo); } return 0; }

__EOF__

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