问你有每个 n 个点的有哈密顿回路的竞赛图的期望哈密顿回路数。 其中哈密顿回路就是从一个点出发不重复的经过每个点最终回到自己的路径。 竞赛图就是任意两点之间都有一条有向边。
射命丸文的笔记
题目大意
问你有每个 n 个点的有哈密顿回路的竞赛图的期望哈密顿回路数。
其中哈密顿回路就是从一个点出发不重复的经过每个点最终回到自己的路径。
竞赛图就是任意两点之间都有一条有向边。
思路
考虑求出所有图的哈密顿回路总数和有多少个图有哈密顿回路。
前者比较好求,你就选一套路径 n!n=(n−1)!,然后其它边就可以随意了 2(n(n−1)2−n)。
那接着问题就是后者,考虑一个容斥 DP:
fn=2n(n−1)2−n−1∑i=1fi(ni)2(n−i)(n−i−1)2
这里稍微解释一下,你每次就枚举一个块和其他的部分不能连通,那其他的部分是要连通的所以是 fi(这里枚举的是其他的部分),然后这个块里面随便选所以是 2(n−i)(n−i−1)2,然后它们这两个部分之间肯定是只有从一边连向另一边,不用乘二是因为乘二到时就会有重复计算。
然后你考虑优化:
发现在 i=n 的时候 (ni)2(n−i)(n−i−1)2 这个东西是 1,那我们完全可以把右边的减移到左边,从而变成这个:
n∑i=1fi(ni)2(n−i)(n−i−1)2=2n(n−1)2
然后我们拆开组合数:
n∑i=1fin!i!(n−i)!2(n−i)(n−i−1)2=2n(n−1)2
移项:
n∑i=1fii!2(n−i)(n−i−1)2(n−i)!=2n(n−1)2n!
然后发现我们设 F=∑ifii!xi,G=∑i2i(i−1)2i!xi,然后就变成了这个:
G(x)=F(x)G(x)+1(这个 +1 是因为上面的 1∼n 的时候是没有第 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__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现