2025.02.25 CW 模拟赛 C. 有向图
C. 有向图
题意
初始有一张含有 \( n \) 个点的有向图, 你可以不断向其中加入至多\( n(n-1) \)条边, 不允许加入重边或者自环.
定义一个长度为 \( m \) 的序列 \( \{a_i\} \) 为一个 SCC 序列, 当且仅当存在一种加边方案, 使得加入\( i \)条边后图中恰好存在 \( a_i \) 个强连通分量 (两个点在一个强连通分量中当且仅当他们能够相互到达).
给定 \( n \), 你需要求出当序列长度分别为 \( 1 \cdots n(n-1) \)时, 有多少种 SCC 序列, 由于答案可能很大, 需要对于一个数字取模. 为了防止打表, 模数是输入的.
思路
对于每一个 \(n\), 我们考虑其字典序最大的一个序列. \((\)例如 \(n = 4\), 字典序最大的序列为 4 4 4 4 4 4 3 2 2 1 1 1
\()\).
记这个序列为 \(mx\), 考虑这个序列是怎样形成的. 显然, 对于前 \(\frac{n(n - 1)}{2}\) 项都是 \(n\), 如下图所示, 我们在每相邻两个点间连一条有向边. 如果再继续连, SCC 的数量必定减少, 动手画图可以发现, 序列后 \(\frac{n(n - 1)}{2}\) 项最大分别为 1 个 \(n - 1\), 2 个 \(n - 2\) ...
现在假设我们已经知道了一个前缀 SCC 序列, 那么这一项最小是多少? 不难发现, 答案是 \(i - k + 1\), 其中 \(i, k\) 分别表示「前缀序列长度」和「序列中不同数的个数」. 可以使用构造进行证明. 至于最大值, 就是上一位填的值和这一位的 \(mx_i\) 取一个 \(\min\) 即可.
这样我们有一个朴素的 DP 式子: \(f_{i, j, k}\) 表示当前是序列的第 \(i\) 项, 填的是 \(j\), 序列总共有 \(k\) 个不同数的方案数. 那么有
再使用前缀和优化可以做到 \(\mathcal{O}(Tn^4)\).
代码
#include "iostream"
#include "cstring"
using namespace std;
constexpr int N = 101, M = 1e4 + 1;
int n, mod;
int f[2][N][N], pre[N][N], mx[M];
int val(const int y) { return y >= mod ? y - mod : y; }
void addon(int &x, const int y) {
if (x >= mod - y) x = x - mod + y;
else x += y;
}
void print(const int x) {
if (x > 9) print(x / 10);
putchar(x % 10 + '0');
}
void init() {
scanf("%d %d", &n, &mod);
int lim = n * (n - 1) >> 1;
for (int i = 1; i <= lim; ++i) mx[i] = n;
for (int i = n - 1; i; --i)
for (int j = (n - i) * (n - i - 1) / 2 + 1, k = j; j <= k + n - i; mx[lim + (j++)] = i);
}
void calculate() {
memset(f, 0, sizeof f);
f[1][n][1] = 1, printf("1 ");
for (int i = 2, lim = n * (n - 1); i <= lim; ++i) {
int ans = 0;
for (int j = 1, _ = min(i, n); j <= _; ++j) {
pre[j][1] = f[!(i & 1)][1][j];
for (int k = 2; k <= mx[i - 1]; ++k)
pre[j][k] = val(pre[j][k - 1] + f[!(i & 1)][k][j]);
}
for (int j = 1; j <= mx[i]; ++j)
for (int k = 1, _ = min(i, n - j + 1); k <= _; ++k) {
int l = max(j, n - i + k - 1);
if (l > j) continue;
f[i & 1][j][k] = val(pre[k - 1][mx[i - 1]] - pre[k - 1][l + (l == j) - 1] + mod);
if (l == j) addon(f[i & 1][j][k], f[!(i & 1)][j][k]);
addon(ans, f[i & 1][j][k]);
}
print(ans), putchar(' ');
}
puts("");
}
void solve() {
int T; scanf("%d", &T);
while (T--) {
init();
calculate();
}
}
int main() {
solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现