10.12 模拟赛

题解

A. 选择排序

粘过来题面的代码:

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++)
        if (a[i] < a[j])
            swap(a[i], a[j]);
}

考虑如何计算整个串的答案。

首先暴力做一遍 \(i = 1\)。此时序列中的最大值一定会被交换到 \(a_1\)

然后将刚才的答案加上目前 \(a\) 中的逆序对数量就是真实答案。具体的,我们需要对于每个 \(j\),计算 \(j\) 之前有多少\(> a_j\)

为啥?模拟+找规律。

for (int x = 1; x <= n; ++ x ) {
	for (int i = 1; i <= x; ++ i ) a[i] = b[i];
	
	int res = 0;
	for (int j = 1; j <= x; ++ j ) {
		if (a[1] < a[j]) {
			swap(a[1], a[j]);
			res ++ ;
		}
	}
	
	for (int i = 2; i <= x; ++ i ) res += /* i 前面有多少种数 > a[i] */;
	
	cout << res << " \n"[x == n];
}

用树状数组实现可以做到整体 \(\mathcal O(n^2)\) 的复杂度。现在考虑优化上面的代码。

for (int x = 1; x <= n; ++ x ) {
	for (int i = 1; i <= x; ++ i ) a[i] = b[i];
	
	int res = 0;
    
    // 优化这个循环比较简单。注意到从 x -> x+1 时,我们只需要在上一次的基础上多做一次 j = x+1 即可。
	for (int j = 1; j <= x; ++ j ) {
		if (a[1] < a[j]) {
			swap(a[1], a[j]);
			res ++ ;
		}
	}
    // 也就是说,从 x -> x+1 时 a[1 ~ x] 中至多只会有 a[1] 会发生变化,其他位置一定不会发生改变
	
    // 别忘了每次上面的循环结束后,a[1] 都是目前的最大值
    // 我们知道当枚举的是 x 时 a[1] 是最大值。那么当枚举的变成 x+1 时,如果上面 j = x+1 的没有执行(即 a[x + 1] < a[1],即 a[1] 仍然是最大值),那么前面所有数对答案的贡献都没有发生改变。
    // 否则,由于 a[1] 可能会出现多次,考虑下面的图。先我们即将将第一个位置从 a[1] 变成 a[x + 1],且 a[x + 1] > a[i]
    // 显然绿色区间内的数对答案的贡献没有变化。因为 a_1 本来就比它们大,而我们是把 a_1 替换成一个更大的数。
    // 但是蓝色区间内的数对答案的贡献会增加 1。这是因为原先它们前面有两个 a_1,但是我们只计算了一个的贡献。现在我们把其中一个 a_1 改变了,也就是说原来的两个数现在都要贡献一次,所以这些数的贡献会大 1。
	for (int i = 2; i <= x; ++ i ) res += /* i 前面有多少种数 > a[i] */;
	
	cout << res << " \n"[x == n];
}

image-20241012163924009

所以我们需要维护一种数据结构,支持查询某种数字在序列中第一次和第二次的出现位置,以及删除第一个数。双端队列(deque)或链表(list)即可。

加上树状数组总复杂度 \(\mathcal O(n \log n)\)

posted @ 2024-10-12 17:19  2huk  阅读(5)  评论(0编辑  收藏  举报