CF1089I
考虑用总数(n!)减去不合法的排列数。我们现在要研究不合法的排列长什么样。
称【将子段排序后是连续的一段数值】的子段称为不合法子段。那么合法的排列,就是不存在长度在 [2,n−1] 中的不合法子段的排列。
称一个不合法子段是极长的,当且仅当不存在另一个长度小于 n 的不合法子段包含它。发现,两个不合法子段如果有交,则它们的并也是不合法子段。因此,不合法的排列只有如下两种可能:
- 由两段极长不合法子段组成,这两段不合法子段可能相交。形式化地,若这两个子段分别是 [l1,r1],[l2,r2],则 l1=1,r2=n,且 r1≥l2−1。
- 由三段及以上的极长不合法子段拼成,它们互不相交。形式化地,若这些子段分别是 [l1,r1],[l2,r2],⋯,[lk,rk],则 l1=1,rk=n,且 ∀i,1≤i<k,ri+1=li+1。
上述两种情况,不重不漏地刻画了所有不合法排列。因此只需要对这两种情况分别计数。
设长度为 i 的合法排列数为 fi,也就是答案。
第一种不合法排列:发现两个子段中,必有一个包含的数值为 1,2,⋯,i(其中 i 是子段长度)。不妨假设它是左边的子段,然后把方案数乘以 2 即可。设 hi 表示有多少长度为 i 的排列,满足它的任何长度 <i 的前缀,不是【数值的一个前缀】。形式化地 ∀j,1≤j<i,{pk|k≤j}≠{1⋯j}。求 hi,可以继续使用【总数减不合法数量】的思想:hi=i!−∑i−1j=1hj×(i−j)!。求出 hi 后,第一种不合法的排列数量是 2×∑n−1i=1hi×(n−i)!。也就是枚举了左边子段的长度,后面的数可以任意排列:因为不管左边怎么样,后面任意排列,至少自己内部是一个不合法子段(i+1⋯n,连续的数值);当然,它可能还能向左延伸一点,也就是两个子段有交。
第二种不合法排列:考虑设 gi,j 表示 i 个数,划分为 j 个不合法子段的方案数(不一定极长)。则 gi,j=∑ik=1gi−k,j−1×k!,其中 k 是枚举最后一个不合法子段的长度(这个子段内部可以任意排列,也就是 k! 种方案)。初始值是 g0,0=1。注意,这里没有考虑子段的相对顺序,也就是最后一个子段里的数值,默认为 i−k+1⋯i。给此时的 j 个子段依次编号为 1⋯j。现在要求 j 个子段都是极长的,那么相当于 1⋯j 这些子段的子段,不能有连续数值,也就是:∑n−1j=3gn,j×fj。
综上所述:fn=n!−2×∑n−1i=1hi×(n−i)!−∑n−1j=3gn,j×fj。
预处理答案,时间复杂度为 O(n3)。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 405;
int T;
int n, mod;
int fac[N];
int f[N], h[N], g[N][N];
void add(int &a, int b) { a += b; if (a >= mod) a -= mod; }
void sub(int &a, int b) { a -= b; if (a < 0) a += mod; }
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % mod;
h[1] = 1;
for (int i = 2; i <= n; ++i) {
h[i] = fac[i];
for (int j = 1; j < i; ++j)
sub(h[i], 1ll * h[j] * fac[i - j] % mod);
}
g[0][0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= i; ++j)
for (int k = 1; k <= i; ++k)
add(g[i][j], 1ll * g[i - k][j - 1] * fac[k] % mod);
f[1] = 1, f[2] = 2;
for (int i = 3; i <= n; ++i) {
for (int j = 1; j < i; ++j)
add(f[i], 1ll * h[j] * fac[i - j] % mod);
add(f[i], f[i]);
for (int j = 3; j < i; ++j)
add(f[i], 1ll * g[i][j] * f[j] % mod);
f[i] = (fac[i] - f[i] + mod) % mod;
}
}
void solve() {
scanf("%d", &n);
printf("%d\n", f[n]);
}
int main() {
scanf("%d%d", &T, &mod);
init(400);
while (T--) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话