D. Token Removing
D. Token Removing
Define a sequence of integers $a$ valid if and only if $\forall 1 \le i \le n, 0 \le a_i \le i$.
Define the weight $f(a)$ of a valid sequence $a$ of length $n$:
- Initially, a token is placed at each integer point in the closed segment $[1, n]$ on the number axis.
- Perform $n$ operations in sequence. During the $i$-th operation, if $a_i \ne 0$, remove a token in the closed segment $[a_i, i]$ that has not been removed; otherwise, do nothing.
- $f(a)$ is the number of ways to remove tokens. Two ways are considered different if there exists a $t$ such that the positions of the tokens removed by the two ways are different at the $t$-th operation.
For example, $f([0, 2, 1]) = 2$, because we can remove tokens on $2, 1$ or $2, 3$ in sequence.
JT gives you two integers $n, m$ and asks you to find the sum of the weights over all $(n + 1)!$ valid sequences of length $n$. Since the answer may be too large, print it modulo $m$.
Input
Each test contains multiple test cases. The first line contains the number of test cases $t$ ($1 \le t \le 1000$). The description of the test cases follows.
The only line of each test case contains two integers $n$ and $m$ ($1 \le n \le 5000, 10^8 \le m \le 1.01 \cdot 10^9$) — the length of valid sequences, and the modulus.
It is guaranteed that the sum of $n^2$ over all test cases does not exceed $2.5 \cdot 10^7$.
Output
For each test case, output an integer — the sum of the weights over all $(n + 1)!$ valid sequences of length $n$, modulo $m$.
Example
Input
6
1 1000000007
2 1000000007
3 1000000007
4 1000000007
5 1000000007
114 514191981
Output
2
7
37
273
2672
393775292
Note
In the first test case, valid sequences are $[0]$ and $[1]$, and the answer is $f([0]) + f([1]) = 1 + 1 = 2$.
In the second test case, valid sequences are $[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]$. The weight of $[0, 1]$ is $2$, and the others are $1$, so the answer is $5 \cdot 1 + 1 \cdot 2 = 7$.
解题思路
好久没写博客了,一方面近一个月要准备考试,另一方面现在基本打一场掉一场,打击太大想退坑了。纵观下来上半年几个主要平台都在大幅掉分,由于 AI 水平越来越强,现在的思维题的比重越来越大,而我最不会的就是思维题,练了几年一点提升都没有,排名经常垫底都见怪不怪了。可能过不了几个月就真退坑了,实在打不下去了。
容易发现无论是一个给定序列 $a$ 的 $f(a)$,或者所有合法的序列 $a$ 的 $f(a)$ 的和,都不好求。由于所求结果可以通过贡献得到,即反过来考虑每个合法的 token 取法 $p$ 可以由多少个不同的合法序列 $a$ 得到,记为 $g(p)$,那么所有合法的 $g(p)$ 的和与所有合法的 $f(a)$ 的和是相等的。因此我们只需考虑如何求合法 token 取法 $p$ 的 $g(p)$。
实际上一个给定的合法 $p$ 也不好求 $g(p)$,但我们可以确认的是对于每个 $p_i \, (0 \leq i < k)$,其必然只能通过 $a_{p_i}$ 后缀中的某个 $a_{j}$(即 $p_i \leq j \leq n$)来获取 $p_i$ 这个 token,且 $a_j$ 的取值也要满足 $1 \leq a_j \leq p_i$。之所以提这一点,是因为对于含相同种类元素但排列不同的合法 $p$,都适用这一规则,可以一次性算出这些 $p$ 的 $g(p)$ 的和。
由于每个 $p_i$ 被哪个 $a_j$ 获取由不同的后缀决定,因此我们应该从大到小(即 $p_0 > p_1 > \cdots > p_{k-1}$)来计算方案数(获取每个含相同种类元素但排列不同的合法 $p$ 的不同序列 $a$ 的数量的总和)。具体地,要获取 $p_0$ 这个 token,这样的 $a_j$ 有 $(n-p_0+1) \cdot p_{0}$ 种。在确定获取 $p_0$ 的 $a_j$ 后,再考虑获取 $p_1$,这样的 $a_j$ 有 $(n-p_1+1-1) \cdot p_{1}$ 种。同理分析,在确定获取 $p_0, \ldots p_{i-1}$ 的 $a_j$ 后,获取 $p_i$ 的 $a_j$ 有 $(n-p_i+1-i) \cdot p_{i}$ 种。因此方案数就是 $\prod\limits_{i=0}^{k-1}{(n-p_{i}+1-i) \cdot p_{i}}$。
因此现在我们只需枚举集合 $S = \{1,2,\ldots,n\}$ 的所有子集 $s \subseteq S,$(其中 $p_{k} \in s, p_{k} > p_{k+1} \, (0 \leq k < |s|-1)$),然后计算上面的式子并最后求和即可,即 $\sum\limits_{s \subseteq S}{\prod\limits_{k=0}^{|s|-1}{(n-p_{k}+1-k) \cdot p_{k}}}$。该结果可以通过 dp 来计算。
定义 $f(i,j)$ 表示考虑 $S = \{i,i+1,\ldots,n\}$ 的所有大小为 $j$ 子集 $s$ 关于 $\sum\limits_{s \subseteq S}{\prod\limits_{k=0}^{j-1}{(n-p_{k}+1-k) \cdot p_{k}}}$ 的结果。根据子集 $s$ 中是否包含 $i$ 来进行状态划分,如果不含有 $i$,对应部分的结果是 $f(i+1,j)$;否则含有 $i$,对应部分的结果是 $$\sum\limits_{s \subseteq S}\left({\left(\prod\limits_{k=0}^{j-2}{(n-p_{k}+1-k) \cdot p_{k}}\right) \cdot \left((n-i+1-(j-1)) \cdot i\right)}\right) = f(i+1,j-1) \cdot (n-i+1-(j-1)) \cdot i$$
因此状态转移分方程就是 $f(i,j) = f(i+1,j) + f(i+1,j-1) \cdot (n-i+1-(j-1)) \cdot i$。
最后答案就是 $\sum\limits_{i=0}^{n}{f(1,i)}$。
AC 代码如下,时间复杂度为 $O(n^2)$:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5005;
int f[N][N];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n + 1; i++) {
memset(f[i], 0, n + 1 << 2);
}
f[n + 1][0] = 1;
for (int i = n; i; i--) {
for (int j = 0; j <= n - i + 1; j++) {
f[i][j] = f[i + 1][j];
if (j) f[i][j] = (f[i][j] + f[i + 1][j - 1] * (n - i + 2ll - j) % m * i) % m;
}
}
int ret = 0;
for (int i = 0; i <= n; i++) {
ret = (ret + f[1][i]) % m;
}
cout << ret << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
参考资料
Codeforces Round 1035 (Div. 2) Editorial:https://codeforces.com/blog/entry/144494
Codeforces Round 1035 (Div. 2)题解(原创):https://zhuanlan.zhihu.com/p/1925154913115146099
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/18980409

浙公网安备 33010602011771号