[AGC054C] Roughly Sorted 题解

发布时间 2023-12-19 12:29:21作者: Creeper_l

题意

定义一种操作为交换 \(a_{i}\)\(a_{i-1}\)。对于一个长度为 \(n\) 的排列,你需要操作若干次,使这个序列变合法,一个序列合法指:满足对于每一个 \(1\le i \le n\),都满足包含 \(a_i\) 的逆序对的个数不超过 \(k\),并且要求最小化操作次数。现在给定一个操作后的序列,询问原序列可能的个数。答案对 \(998244353\) 取模。

思路

我们先来思考如何求从一个序列变为合法序列的最小操作次数。因为操作只能交换相邻两个数,所以一次操作最多只能使逆序对的个数减一。设 \(b_{i}\) 表示包含第 \(i\) 个数的逆序对个数。那么我们每一次都尽量贪心地操作,即操作的 \(i\) 都满足:\(b_{i}>k\) 并且 \(a_{i}<a_{i-1}\)。因为这样操作才能有效果,并且效果最大。

那我们就可以猜想是不是每一次都可以这样操作?那就需要考虑是不是每一次都能找到一个位置满足上述条件。答案是肯定的。考虑用反证法来证明,如果没有位置满足上述条件,即对于每一个 \(b_{i}>k\),都有 \(a_{i-1}< a_{i}\)。那么又因为 \(a_{i-1}< a_{i}\),所以 \(b_{i}\ge b_{i-1}>k\),所以又可以推出 \(a_{i-2} < a_{i-1}< a_{i}\),以此类推,最终可以得到:

\(\begin{cases}a_{1} <a_2<\dots <a_i \\b_i \ge b_i-1 \ge \dots \ge b_1 \end{cases}\)

所以 \(k=0\),但是题目上说 \(k \ge 1\),所以矛盾,于是得证。所以证明到了每一次一定能按照最优的方案去操作,总结操作方式为:每次选择一个 \(i\) 满足 \(b_{i}>k\) 并且 \(a_{i}<a_{i-1}\),交换 \(a_i\)\(a_{i-1}\)

回到原题,再来考虑如何用最终的序列算出原序列可能的个数。首先,我们只考虑 \(b_{i}=k\) 的位置,因为如果 \(b_{i} \ne k\),那么这个数现在的位置和原来的位置是没有变化的。考虑 \(b_i=k\) 的位置,我们会发现原序列中 \(a_{i}\) 这个数的位置一定在 \(i\) 后面,并且在 \(i\) 后面的任意一个位置都可以选择。因为如果在 \(i\) 的前面的话,显然它不会向后移动。所以最终的答案是 \(\prod_{b_{i}=k}^{} (n-i+1)\),总时间复杂度 \(O(n^{2})\),用树状数组可以优化到 \(O(n \log n)\)

Code

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int MAXN = 5e3 + 10;
const int mod = 998244353;
int n,k,a[MAXN],b[MAXN],id[MAXN],p,ans;
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin >> n >> k;
	for(int i = 1;i <= n;i++) cin >> a[i];
	for(int i = 1;i <= n;i++)
	{
		int sum = 0;
		for(int j = 1;j < i;j++) if(a[j] > a[i]) sum++;
		if(sum == k) id[++p] = i; 
	} 
	ans = 1;
	for(int i = 1;i <= p;i++) ans = (ans * (n - id[i] + 1)) % mod;
	cout << ans; 
	return 0;
}