CF2039E - Shohag Loves Inversions
题面
有一个整数数组 \(a = [0, 1]\),可以重复执行以下操作任意多次:
- 假设 \(k\) 是当前数组 \(a\) 中的逆序对的个数。
- 将 \(k\) 插入 \(a\) 中的任意位置,包括开头或结尾。
例如,如果是 \(a = [4, 6, 2, 4]\),那么逆序对数就是 \(k = 3\)。因此,可以在运算后得到以下数组:\([\textbf{3}, 4, 6, 2, 4]\)、\([4, \textbf{3}, 6, 2, 4]\)、\([4, 6, \textbf{3}, 2, 4]\)、\([4, 6, 2, \textbf{3}, 4]\) 和 \([4, 6, 2, 4, \textbf{3}]\)。
给定整数 \(n\),请计算在进行运算后可以得到的长度为 \(n\) 的不同数组的个数,对 \(998\\,244\\,353\) 取模。
题解
又是一个经典组合数学问题。
观察发现,一个数组的逆序对数单调不减,因此我们可以按逆序对数对不同序列分类,接下来我们证明任意两个不同数组不可能得到相同数组。
证明:
记 \(p(a)\) 为数组 \(a\) 的逆序对数,每次插入 \(p(a)\) 后,数组逆序对数单调不减,不妨构建一棵决策树,决策树上的边相同的导向下一个数组,不同的导向不同的数组,因为从根开始边的值是单调的,因此导向的数组也两两不同。
所以我们对不同数组插入一个数得到的一定是不同的数组。
对于 \(a = [0, 1]\) 暂且不论,不妨考虑 \(a = [-2, -1]\) 的情况。
每次插入一个数,他的值一定大于等于当前数组中最大的数,这个比较显然,留给读者证明。
因此,当大于的时候,插入前 \(i\) 个空位一定会导致数组的逆序对变化,插入最后一个空位(向数组后加入一个数)一定不会使逆序对数变化。
我们不妨设 \(f(i)\) 表示长度为 \(i\) 且逆序对数不等于数组最大值的不同数组数,\(g(i)\) 表示长度为 \(i\) 的不同数组,记录一个 \(sum(i)\),表示 \(f(i)\) 里的数组可以插入的位置的个数。
容易发现,\(f(2) = g(2) = 1\),\(sum(2) = 2\),是起始条件。
对于 \(f(i)\) 的转移可以等价于:
对于 \(sum\) 的转移很显然:
对于 \(g(i)\),由于往最后一个位置直接添加数或者向最大值与逆序对数相同的数组的最大值位置加入一个数不改变逆序对数,所以这一部分的值恰好为上一个状态的不同数组个数,即 \(g(i)\) 的值,其余部分等价于 \(f(i+1)\)。
于是接下来,我们考虑 \(a = [0, 1]\),容易发现,对于上述的转移仅仅只改变了一个部分,也就是 \(f(i)\) 的转移,因为第一次出现了逆序对数小于数组最大值的部分,但是这没有关系。
对于 \(sum(i)\) 而言,我们没有改变 \(sum(i)\) 的递推值,仅仅在 \(f(i)\) 的转移上改变了一部分,即删掉最大值小于逆序对数的数组个数。
容易发现这样的数组只有长度为 \(n - 1\) 的数组 \([0, 0, \ldots, 0, 1]\) 向前 \(n - 2\) 个位置插入 \(0\) 得到新的长度为 \(n\) 的数组,这是好计算的,递推式等价于:
\(g(i)\) 定义修正为:往一个位*直接添加一个不改变逆序对数的数或者向最大值与逆序对数相同的数组的最大值位置加入一个数不改变逆序对数,可以发现前者的条件是唯一的,最大值较小的填写在最前列,最大值较大的填写在最后位置,这对我们的转移仍然是相同的。
因此可以完成此题,参考代码如下。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 3, mod = 998244353;
int n, f[N], g[N];void init()
{int sum = 2;f[2] = g[2] = 1;for (int i = 3; i < N; i ++ ){f[i] = (sum - (i - 2) + mod) % mod;(sum += 1ll * f[i] * i % mod) %= mod;g[i] = (g[i - 1] + f[i]) % mod;}
}void solve()
{cin >> n;cout << g[n] << "\n";
}int main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);init();int T = 1;cin >> T;while (T -- ) solve();return 0;
}