QOJ 8351 Ruin the legend 题解
好题,可惜我不会容斥。
记我们把 \(a\) 任意重新排列得到的序列 \(b\)。首先,我们对 \(a\) 进行重排显然不影响方案数,如果对 \(|a_i - a_{i + 1}| = k\) 的 \(a_i, a_{i + 1}\) 连边,由于序列有序,每个点最多前后各自连一条边。会得到若干条链。我们只关心这若干条链的长度(即极长的公差为 \(k\) 的等差子序列长度),设有 \(m\) 段子序列,第 \(i\) 段的长度为 \(l_i\)。
拿到这个题真是没什么头绪来做,这个计算没有相邻差为 \(k\) 的排列太难了,依旧正难则反,考虑容斥。钦定有 \(t\) 个位置满足 \(|p_i - p_{i + 1}| = k\),容斥系数为 \((-1)^{n - j}\) 次方,这里的 \(j\) 为段数,见下。
如果钦定了 \(t\) 个相邻差为 \(k\) 的位置,这些位置可以缩成一段,这样子整个排列被分成了 \(j = n - t\) 段,段内元素相邻差为 \(k\),段间没有钦定的差为 \(k\) 的对。同时由于段有序,方案数需要 \(\times j!\),最后容斥系数求和。在这里,我们通过容斥来拆掉这个选取的难题,同时在容斥过程中我们隐形地使用交替符号 \(-1^{|S|}\) 解决了钦定边之外的出现差为 \(k\) 的情况,换句话说,容斥后,只有那些没有任何相邻差为 \(k\) 的排列被保留,其他所有排列都被抵消,所以出现在钦定的插值为 \(k\) 的边之外位置并不会对答案产生影响。
着手开始 DP。我们需要预处理出 \(g_{w, d, 1/2}\) 表示将长度为 \(w\) 的等差子序列分为 \(d\) 段,其中最后一段的长度为 \(1/ \geq 2\)。
预处理时:
对于 \(1\),我们只需要在最后一段的基础上独立出一个新段并新增一个元素。对于 \(\geq 2\) 的情况,当最后一段只有 \(1\) 时是有两种插入方案的,因为只要求元素差的绝对值等于 \(k\),前后都可以;但是当长度大于等于 \(2\) 时,等差子序列已经有了顺序,所以只能向一个方向插入。
记 \(f_{i, j}\) 表示考虑前 \(i\) 个等差子序列,将排列分成 \(j\) 段的方案数,有:
其含义为前 \(i - 1\) 个子序列分成了 \(j - d\) 段,当前子序列分成了 \(d\) 段,合并得到 \(j\) 段的方案数。
最终答案为:
我写了一个挺有趣的建立链(极长等差子序列)的做法而且比较简单,注意到当 \(a_i - a_j = k\) 时,\(a_i \equiv a_j (\bmod k)\),所以对于 \(a_i \bmod k\) 的余数分类,对每种可能排序之后跑一次统计邻值是否为 \(k\) 即可判断是否为链。
真是好题。
#include <bits/stdc++.h>
#define int long long
constexpr int N = 5e3 + 7;
constexpr int P = 998244353;
int n, k, l;
int ans;
int a[N], fac[N];
int v[N], f[N][N], g[N][N][3];
std::map<int, std::vector<int>> mp;
void init() {
fac[0] = 1;
for (int i = 1; i < N; i++) {
fac[i] = fac[i - 1] * i % P;
}
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
init();
std::cin >> n >> k;
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
g[1][1][1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
g[i][j][1] = (g[i - 1][j - 1][1] + g[i - 1][j - 1][2]) % P;
g[i][j][2] = (2ll * g[i - 1][j][1] % P + g[i - 1][j][2]) % P;
}
}
for (int i = 1; i <= n; i++) {
mp[a[i] % k].push_back(a[i]);
}
for (auto it = mp.begin(); it != mp.end(); it++) {
auto &vec = it->second; int c = 1;
std::sort(vec.begin(), vec.end());
for (int i = 1; i < vec.size(); i++) {
if (vec[i] - vec[i - 1] == k)
c++;
else {
if (c > 0) {
v[++l] = c;
c = 1;
}
}
}
if (c > 0) {
v[++l] = c;
}
}
f[0][0] = 1; int t = 0;
for (int i = 1; i <= l; i++) {
int m = v[i];
for (int j = 0; j <= t; j++) {
for (int d = 1; d <= m; d++) {
f[i][j + d] = (f[i][j + d] + (f[i - 1][j] * (g[m][d][1] + g[m][d][2] % P) % P)) % P;
}
}
t += m;
}
for (int i = 1; i <= n; i++) {
ans = (ans + (f[l][i] * fac[i] * ((n - i) & 1 ? -1 : 1) % P)) % P;
}
std::cout << (ans % P + P) % P << "\n";
return 0;
}

浙公网安备 33010602011771号