P12445 [COTS 2025] 数好图 / Promet
困难 DAG 计数,希望可以讲的详细一些。
首先我们需要考虑题目里这个 \(k\) 的限制,我们不妨直接枚举有 \(k\) 个点,剩下的点只有两种:\(1\) 可达的和 \(1\) 不可达的,DAG 的性质不强,我们似乎只能从分类的角度思考:把点分三类,在 \(1 \rightsquigarrow n\) 路径上的称为 \(1\) 类点,不在 \(1 \rightsquigarrow n\) 路径上但 \(1\) 可以到达的点称为 \(2\) 类点,否则称为 \(3\) 类点。
发现这还是有些性质的,首先 \(3\) 类点可以连向任意点,\(1\) 类点只能连向 \(1, 2\) 类点, \(2\) 类点只能连向 \(2\) 类点,且必须有一个 \(1/2\) 类点连向它。首先 \(1, n\) 为 \(1\) 类点,我们需要计算剩下的点之间的连边。
- \(3 \rightarrow 1/2/3\),由于 \(3\) 类点可以任意连边,假设 \(i\) 是 \(3\) 类点,那么方案数就是 \(2^{n - i}\),这告诉我们 \(3\) 类点的贡献可以单独算。设 \(mul_{i, j}\) 表示 \([i, n - 1]\) 中有 \(j\) 个 \(3\) 类点,那么 \(mul_{i, j} = mul_{i + 1, j} + mul_{i + 1, j - 1}2^{n-i}\)。可以 \(\mathcal{O}(n^2)\) 预处理。那么接下来只要考虑 \(1, 2\) 之间的连边。
- \(1/2 \rightarrow 2\),直接枚举,设 \(dp_{i, j}\) 表示 \(i\) 个 \(1\) 类点,\(j\) 个 \(2\) 类点的方案数,则 \(dp_{i, j} = dp_{i - 1, j} + dp_{i, j - 1}(2^{i + j - 1} - 1)\),后面这个是因为前面任意点都可以连向这个 \(2\) 类点,但是至少有一个点连。
- \(1 \rightarrow 1\),相当于全部点都在路径上的方案数,即 \(k = n\),我们可以转化成 \(1\) 有出度,\(n\) 有入度,且 \([2, n - 1]\) 的所有点都有入度和出度,证明显然。依旧考虑 DP,但是这个不能直接朴素地计数了,直接暴力是 \(\mathcal{O}(n^3)\) 的,那我们可以考虑容斥,\(f_{i, j}\) 表示后 \(i\) 个点,除了 \(i\) 有 \(j\) 个点被强制钦定为入度为 \(0\) 的点,这些点从始至终不能被连边。转移考虑钦定 \(i + 1\) 被不被钦定,如果被钦定,方案是 \(f_{i, j - 1}(2^{i - j - 1} - 1)\),因为 \(i\) 只能选 \((i + 1, n]\) 未被钦定的点连边,否则方案为 \(f_{i, j}(2^{i - j - 1} - 1)\),因为可以向 \([i + 1, n]\) 未被钦定的点连。于是 \(f\) 可以在 \(\mathcal{O}(n^2)\) 内计算。然后考虑容斥成恰好。假设 \(S_i\) 为令 \(i\) 入度为 \(0\) 的 DAG 集合,那么显然 \(f_{i, j} = \sum\limits_{i < a_1 \le a_2 \le \dots \le a_k}|\bigcup\limits_{k=1}^j S_{a_k}|\),所求即 \(|\bigcap S_i|\),容斥一下就是 \(\sum (-1)^jf_{i, j}\)。
由于三种情况都是独立的,枚举 \(1, 2\) 类点个数,分别计算即可。注意这样会在 \(k = 0\) 时出问题,改为全部减去 \(\sum\limits_{k=1}^nans_k\) 即可。时间复杂度 \(\mathcal{O}(n^2)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 2e3 + 10;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
namespace Loop1st {
int n, mod, pw[N * N];
int f[N][N], g[N];
int mul[N][N], dp[N][N];
int ans[N];
void main() {
cin >> n >> mod;
pw[0] = 1;
for (int i = 1; i <= n * n; i++) pw[i] = pw[i - 1] * 2 % mod;
f[1][0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 0; j < i; j++) { // j = i - 1 一定是 0
f[i][j] = (ll)(pw[i - j - 1] - 1) * (f[i - 1][j] + (j ? f[i - 1][j - 1] : 0)) % mod;
}
}
for (int i = 2; i <= n; i++) for (int j = 0; j <= i; j++) {
if (j & 1) g[i] = (g[i] + mod - f[i][j]) % mod;
else g[i] = (g[i] + f[i][j]) % mod;
}
mul[n][0] = 1;
for (int i = n - 1; i >= 2; i--) for (int j = 0; j <= n; j++) {
mul[i][j] = mul[i + 1][j];
if (j) mul[i][j] = (mul[i][j] + (ll)mul[i + 1][j - 1] * pw[n - i] % mod) % mod;
}
dp[1][0] = 1;
for (int i = 1; i <= n; i++) for (int j = (i == 1); j <= n - i; j++) {
dp[i][j] = dp[i - 1][j];
ans[i] = (ans[i] + (ll)mul[2][n - i - j] * dp[i - 1][j] % mod) % mod;
if (j) dp[i][j] = (dp[i][j] + (ll)dp[i][j - 1] * (pw[i + j - 1] - 1) % mod) % mod;
}
for (int i = 2; i <= n; i++) ans[i] = (ll)ans[i] * g[i] % mod;
ans[0] = pw[n * (n - 1) / 2];
for (int i = 2; i <= n; i++) ans[0] = (ans[0] + mod - ans[i]) % mod;
for (int i = 0; i <= n; i++) cout << ans[i] << " \n"[i == n];
}
}
int main() {
// freopen("count.in", "r", stdin);
// freopen("count.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T = 1;
// cin >> T;
while (T--) Loop1st::main();
return 0;
}

浙公网安备 33010602011771号