2020CCPC秦皇岛 H. Holy Sequence
考虑每个数字 \(x\) 第一次出现在位置 \(pos\) 的贡献, 那么就要计算位置 \(pos\) 前后的合法序列的数量了.
定义这两个 \(DP\) 数组:
- \(pre[i][j]\): 当 \(n=i\) 时, \(p_i=j\) 的合法序列数.
- \(suff[i][j]\): \(a_1=j\) (无视 \(a_0\) 的限制) 长度为 \(i\) 的合法序列数.
\(suff\) 数组的推导可以考虑 \(a_2\) 与 \(a_1\) 的大小关系. \(a_2\leq a_1\) 时, 去掉 \(a_2\) 即 \(suff[i-1][j]\); \(a_2=a_1+1\) 时, 去掉 \(a_1\), 即 \(suff[i-1][j+1]\).
然后我们考虑平方怎么处理. 对于 \(x\) 出现了 \(k\) 次的序列, 我们相当于可重复地选两次 \(x\), 这样贡献也是 \(k^2\).
现在枚举每个数字 \(i\) 第一次出现在位置 \(j\) 的贡献, 前缀的方案数是 \(pre[i-1][j-1]\), 后缀由于要选择两次 \(x\), 分四类考虑:
- 两次都选择位置 \(j\).
- 两次选择位置相同但不是 \(j\).
- 两次中一次选择 \(j\), 一次不选 \(j\).
- 两次选择位置不同且都不选择 \(j\).
选择后面有 \(x=0\) ~ \(2\) 个 \(i\) 时, 相当于这个位置确定下来了, 对前后取数没有影响, 可以直接去掉, 剩的位置方案即以 \(i\) 开头, 长度为 \(n-j+1-x\): \(suff[n-j+1-x][i]\) 乘以选择位置的排列数.
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inc(x, l, r) for (int x = l; x <= r; x++)
const int N = 3e3;
int t, n, m;
ll pre[N + 5][N + 5], suff[N + 5][N + 5];
// pre[pos][num] suff[len][num]
int main() {
cin >> t;
inc(p, 1, t) {
cin >> n >> m;
pre[0][0] = 1;
inc(i, 1, n) {
inc(j, 1, i) {
pre[i][j] = (pre[i - 1][j] * j + pre[i - 1][j - 1]) % m;
}
}
inc(i, 1, n) suff[1][i] = 1;
inc(i, 2, n) {
inc(j, 1, n + 1 - i) {
suff[i][j] = (suff[i - 1][j] * j + suff[i - 1][j + 1]) % m;
}
}
vector<ll> res(n + 1);
inc(i, 1, n) { // num
inc(j, i, n) { // pos
ll tmp = suff[n - j + 1][i]; //情况一
if (j < n)
tmp =
(tmp + 3 * (n - j) * suff[n - j][i]) % m; //情况二, 三
if (j < n - 1)
tmp =
(tmp + (n - j) * (n - j - 1) % m * suff[n - j - 1][i]) %
m; //情况四
res[i] = (res[i] + pre[j - 1][i - 1] * tmp) % m;
}
}
cout << "Case #" << p << ":\n";
inc(i, 1, n) {
cout << res[i] << " \n"[i == n];
}
}
}