1 条题解
-
0
题解
题意简述
给定一个部分缺失(用 表示)的排列 (元素为 的排列),需要将所有 填充为剩余数字,得到所有可能的合法排列。
定义排列的值为所有非空连续子段的 之和。
求所有可能排列的值的总和,对 取模。
思路分析
1. 转化 的贡献
对于一段区间 , 等于最小的非负整数 使得 不在该区间中出现。
$$\operatorname{mex}([i,j]) = \sum_{x=0}^{n-1} [\text{$0 \sim x$ 全都出现在 $[i,j]$}] $$
这等价于:对于每个 ,如果 全都出现在区间中,则 对 有贡献 。
所以:(这里 从 到 求和,但实际上当 超过区间的长度时自动为 0)
2. 整体计数框架
设所有排列总数为 ,其中 是 的数量。
$$\sum_{\text{所有排列}} \sum_{1\le i\le j\le n} \operatorname{mex}([i,j]) $$
我们要求:交换求和顺序:
$$\sum_{1\le i\le j\le n} \sum_{\text{所有排列}} \operatorname{mex}([i,j]) $$再对每个固定 ()统计:有多少排列使得 包含 全部。
3. 固定 的计数
设:
- 在固定区间 中,有 个 位置。
- 在 中,有 个数字没有在 的已知位置中出现(即这些数字需要被填入该区间中的 中)。
- 剩下 个 在区间外,它们必须填入 之外的数字。
则方案数为:
- 从 个区间内 中选出 个位置来放这 个特定数字:。
- 这 个数字可以任意排列在这 个位置上:。
- 剩下的 个 (区间外)任意排列剩下的 个数字:。
所以贡献为:
注意:要求区间 必须包含所有 中已知出现的数字,即已知数字的位置必须在 内,否则方案数为 0。
4. 高效枚举
直接枚举 是 ,不可行。
我们观察到 只影响 (需填充的数字数量),而 取决于 是否包含所有已知的 。定义:
- 为前缀中 的数量。
- 对每个区间 ,记 ,如果所有已知数字都在区间内,则 。
- 则对于 ,$c_2 = \text{在 中未出现的数字数}x[i,j]$ 无关。
- 对于 ,区间不包含所有 ,贡献为 0。
因此,我们可以先统计所有区间 ,对每个区间计算 和区间内 的数量 ,然后对 分别加上 $\binom{c_1}{c_2(x)} \cdot c_2(x)! \cdot (t-c_2(x))!$ 的贡献。
但这样还是 。优化:按 和 分组累加。
5. 差分优化
设 表示“区间内 数量为 ,且 ”的区间数量。
$$\binom{c_1}{c_2(x)} \cdot c_2(x)! \cdot (t-c_2(x))! $$
则 对 的贡献为:其中 = 在 中未出现的数字数量(全局常数,可预处理)。
我们如何得到 ?
对每个区间 :- 计算 c_1 = \text{区间内 -1 的数量}。
- 计算 (若全部在,则 )。
- 则该区间对 各加 1。
这可以用差分数组实现:对每个 ,维护一个差分数组 ,对区间 加 1,即:
$$diff[0] \mathrel{+}= 1,\quad diff[x_0] \mathrel{-}= 1 $$最后前缀和得到 。
6. 枚举区间
我们要快速枚举所有 并求出 和 。
-
可用前缀和 轻松得到。
-
:已知数字的最小不在区间内的值。
可以分别从左到右和从右到左维护前缀最小缺失值。具体方法:枚举左端点 ,从右往左扫描 ,维护两个值:
- :左边部分()已知数字的最小值(实际是“不在 中的已知数字”需要看两端)。
更简单的做法:
设 = 已知数字在 左侧的最小值, = 已知数字在 右侧的最小值。
则不在区间内的已知数字的最小值 = 。
但 在 向左移动时更新, 固定对固定 。
算法:
- 预处理每个位置右边已知数字的最小值 。
- 枚举 ,设 (表示左侧无已知数字)。
- 从 向下到 :
- 更新 (若 不是 -1,则 更新为 )。
- 。
- 。
- 对 差分加 1。
- 如果 不是 -1,更新 。
- :左边部分()已知数字的最小值(实际是“不在 中的已知数字”需要看两端)。
更简单的做法:
设 = 已知数字在 左侧的最小值, = 已知数字在 右侧的最小值。
7. 最终计算
预处理:
- 阶乘 。
- 组合数 。
- = 中未出现的已知数字个数()。
则: [ \text{ans} = \sum_{x=0}^{n-1} \sum_{c_1 = cnt[x]}^{t} d[c_1][x] \cdot \binom{c_1}{cnt[x]} \cdot fac[cnt[x]] \cdot fac[t-cnt[x]] \pmod{mod} ] 因为当 时,无法放下所有需要的数字,贡献为 0。
复杂度
- 枚举所有 :。
- 差分与求和:。
- 总复杂度 ,对 可行。
代码
#include <bits/stdc++.h> using namespace std; const int maxn = 5050; const int mod = 1000000007; int n, a[maxn], fac[maxn], C[maxn][maxn], b[maxn], d[maxn][maxn]; bool vis[maxn]; void solve() { cin >> n; for (int i = 0; i <= n; ++i) { vis[i] = 0; for (int j = 0; j <= n; ++j) d[i][j] = 0; } fac[0] = 1; for (int i = 1; i <= n; ++i) { cin >> a[i]; fac[i] = 1LL * fac[i - 1] * i % mod; b[i] = b[i - 1] + (a[i] == -1); if (a[i] != -1) vis[a[i]] = 1; } // 组合数 for (int i = 0; i <= n; ++i) { C[i][0] = C[i][i] = 1; for (int j = 1; j < i; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod; } int mn1 = n; // 左侧已知数字的最小值 for (int i = 1; i <= n; ++i) { int mn2 = n; // 右侧已知数字的最小值 for (int j = n; j >= i; --j) { int x = b[j] - b[i - 1]; // c1 int y = min(mn1, mn2); // x0 d[x][0] = (d[x][0] + 1) % mod; d[x][y] = (d[x][y] - 1 + mod) % mod; if (a[j] != -1) mn2 = min(mn2, a[j]); } if (a[i] != -1) mn1 = min(mn1, a[i]); } // 前缀和得到 d[c1][x] for (int i = 0; i <= b[n]; ++i) for (int j = 1; j <= n; ++j) d[i][j] = (d[i][j] + d[i][j - 1]) % mod; int ans = 0, cnt = 0; for (int x = 0; x < n; ++x) { cnt += (!vis[x]); // c2 = cnt for (int c1 = cnt; c1 <= b[n]; ++c1) { ans = (ans + 1LL * C[c1][cnt] * fac[cnt] % mod * fac[b[n] - cnt] % mod * d[c1][x]) % mod; } } cout << ans << '\n'; } int main() { ios::sync_with_stdio(0); cin.tie(0); int T; cin >> T; while (T--) solve(); return 0; }
- 1
信息
- ID
- 7073
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 3
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者