P12445 [COTS 2025] Promet 题解

Description

给定正整数 NN 和素数 PP

K=0,1,,N\forall K=0,1,\ldots,N,求出满足以下条件的简单有向图的数量:

  • 图中仅包含 iji\to j1i<jN1\le i\lt j\le N)的边;
  • 满足以下条件的点 uu 恰好有 KK 个:
    • 存在 1u1\to uuNu\to N 的路径。

只需要输出答案对 PP 取模后的结果。

2N20002\le N\le 2\,000

Solution

首先考虑 k=nk=n 怎么求。

容易发现这个一个连边方案合法的充要条件就是 [1,n1][1,n-1] 内的点都有出度,[2,n][2,n] 内的点都有入度,考虑先保证每个点都有入度,并容斥出度为 00 的点的个数。

具体地,设 fi,jf_{i,j} 表示前 ii 个点,钦定 iji-j 个点出度为 00 的方案数,转移即为:fi,jfi1,j(2j1)+fi1,j1(2j11)f_{i,j}\leftarrow f_{i-1,j}\cdot(2^j-1)+f_{i-1,j-1}\cdot(2^{j-1}-1)。求出 fi,jf_{i,j} 之后再二项式定理求出恰好一个点(因为 nn 一定没有出度)出度为 00 的方案数即可。设 ii 个点的方案数为 hih_i

然后考虑 kk 更小的情况怎么做。

先对点进行分类:

  1. 既存在 1u1\to u 也存在 unu\to n 的路径。
  2. 只存在 1u1\to u 但不存在 unu\to n 的路径。
  3. 不存在 1u1\to u 的路径。

容易发现,对于 11 类点往前只能连 1,31,3 类点,且 11 类点至少一个。22 类点能连 1,2,31,2,3 类点,且 1,21,2 类点至少一个。33 类点能连 1,31,3 类点。

这里不考虑 11 类点内部的贡献,因为这个可以已经得到了,为 hnh_n

注意到 33 类点作为起点时,终点是没有限制的,并且所有 33 类点参与的边中,33 类点一定是起点。所以设 pip_i 表示第 ii33 类点的位置,那么所有 33 的贡献即为 2(npi)2^{\sum(n-p_i)}。于是只要求出 1,21,2 类点的贡献后,乘上预处理出来的 33 类点贡献即可。设 ii33 类点的贡献为 sis_i

对于 1,21,2 类点之间的贡献,考虑设 gi,jg_{i,j} 表示前 ii 个点,现在有 jj11 类点的贡献,转移为:gi,jgi1,j1+gi1,j(2i1)g_{i,j}\leftarrow g_{i-1,j-1}+g_{i-1,j}\cdot (2^i-1)

然后枚举 1,21,2 类点的总数 ii,和 11 类点的个数 jj 并让 resjgi1,j1snihjres_j\leftarrow g_{i-1,j-1}\cdot s_{n-i}\cdot h_j 即可(这里只有 gi1,j1g_{i-1,j-1} 的原因是最后一个点必须是 11 类点)。

时间复杂度:O(n2)O(n^2)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 2e3 + 5;

int n, mod;
int pw[kMaxN], f[kMaxN][kMaxN], g[kMaxN], h[kMaxN], s[kMaxN], res[kMaxN];

int qpow(int bs, int64_t idx = mod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % mod)
if (idx & 1)
ret = (int64_t)ret * bs % mod;
return ret;
}

inline int add(int x, int y) { return (x + y >= mod ? x + y - mod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + mod); }
inline void inc(int &x, int y) { (x += y) >= mod ? x -= mod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += mod : x; }
inline int getop(int x) { return (~x & 1) ? 1 : (mod - 1); }

void prework() {
pw[0] = 1;
for (int i = 1; i <= n; ++i) {
pw[i] = 2ll * pw[i - 1] % mod;
}
}

void gets() {
static int f[kMaxN][kMaxN];
f[1][0] = f[1][1] = 1;
for (int i = 2; i <= n; ++i) {
for (int j = 0; j <= i - 1; ++j) {
int val = f[i - 1][j];
inc(f[i][j], 1ll * val * (pw[j] - 1) % mod);
inc(f[i][j + 1], 1ll * val * (pw[j] - 1) % mod);
}
}
for (int i = 2; i <= n; ++i) {
// for (int j = 0; j <= i; ++j)
// for (int k = 0; k < j; ++k)
// dec(f[i][j], 1ll * f[i][k] * C[i - k][i - j] % mod);
// for (int j = 0; j <= i; ++j) inc(s[i], 1ll * getop(i - j) * f[i][j] % mod);
// for (int j = 0; j <= i; ++j) std::cerr << f[i][j] << " \n"[j == i];
// std::cerr << s[i] << '\n';
// s[i] = f[i][i - 1];
for (int j = 0; j <= i - 1; ++j) inc(s[i], 1ll * getop(i - 1 - j) * (i - j) % mod * f[i][j] % mod);
}
}

void dickdreamer() {
std::cin >> n >> mod;
prework();
// g[i] : xuan i ge 3 de gong xian
g[0] = 1;
for (int i = 2; i <= n - 1; ++i) {
for (int j = i - 1; j; --j) inc(g[j], 1ll * pw[n - i] * g[j - 1] % mod);
}
gets();
// for (int i = 0; i <= n - 2; ++i) std::cerr << g[i] << " \n"[i == n - 2];
f[1][1] = 1;
for (int i = 2; i <= n; ++i) {
for (int j = 1; j <= i - 1; ++j) {
int val = f[i - 1][j];
inc(f[i][j + 1], val);
// std::cerr << "??? " << j + 1 << ' ' << i << ' ' << 1ll * val * g[n - i] % mod << '\n';
inc(h[j + 1], 1ll * val * g[n - i] % mod);
if (i != n) {
inc(f[i][j], 1ll * val * (pw[i - 1] - 1) % mod);
}
}
}
for (int i = 2; i <= n; ++i) res[i] = 1ll * h[i] * s[i] % mod;
res[0] = qpow(2, n * (n - 1) / 2);
for (int i = 1; i <= n; ++i) dec(res[0], res[i]);
for (int i = 0; i <= n; ++i) std::cout << res[i] << " \n"[i == n];
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P12445 [COTS 2025] Promet 题解
https://sobaliuziao.github.io/2025/06/17/post/514f1f9c.html
作者
Egg_laying_master
发布于
2025年6月17日
许可协议