C++ 知识点选讲 9——骗分试例 1(从暴力开始骗着骗着 AC 了?)

让我们从一道简单的题目入手,看看如何优化自己的暴力。

数对

题目背景

猫猫吃掉了题目背景。

题目描述

给出一个序列 \(a\),长度为 \(n\)。

小 A 对这个数列发明了一个新定义:淘气对。小 C 将来解释一下这是什么意思。

淘气对定义:
当序列中有两个位置不同数。设在前面的数为 \(a_x\),后面的数为 \(a_y\),如果 \(a_x > a_y\) 且 \(x < y\),这个数对 \((x,y)\) 就是“淘气对”。

小 B 想知道,序列 \(a\) 的下标 \(l\) 至 \(r\) 中有几对“淘气对”。当然,下标从 \(1\) 开始。

众所周知,有个叫 rand 的东西,为防止你用这个东西蒙混过关,小 B 会问你 \(q\) 次。

输入格式

第一行,两个整数 \(n\) 和 \(q\),以空格隔开。
第二行,\(n\) 个整数,第 \(i\) 个数表示 \(a_i\)。
第三至 \(2+q\) 行,每行两个整数 \(l\) 和 \(r\),以空格隔开。

输出格式

\(q\) 行,每行一个整数,表示序列 \(a\) 的 \(l \sim r\) 下标中有多少对逆序对。

输入输出样例 #1

输入 #1

4 1
3 2 3 2
1 4

输出 #1

3

说明/提示

$n \le $ \(q \le\) \(l\le\) \(r\le\) \(a_i\le\) 分数 特殊性质
\(2\) \(1\) \(1\) \(2\) \(10^9\) \(5\) 无
\(4\) \(1\) \(1\) \(4\) \(3\) \(1\) A
\(10^3\) \(10\) \(1\) \(10^3\) \(10^9\) \(10\) B
\(10^4\) \(10^3\) \(1\) \(2\) \(10^9\) \(13\) 无
\(10^4\) \(10^3\) \(1\) \(10^4\) \(10^9\) \(56\) 无
\(2\times10^4\) \(10^5\) \(1\) \(2\times10^4\) \(10^{14}\) \(9\) C

特殊性质 A:样例。
特殊性质 B:为 \(O(qn^2)\) 预留分数。
特殊性质 C:官方题解的正常时间复杂度可通过。

本题不是捆绑测试,请放心食用。

本题的时限由评测机所定。


好吧如果你在模拟赛里碰到了这道题目,因为你不会写树状数组、线段树或归并排序,你想尽可能多地骗分。

Part 1

首先,看到特殊性质 A,发现是样例,所以输出 3。

当前得分:\(1\)

Part 2

看到第一档,发现 \(n \le 2\)。如果你没有学过数组,如果 \(r=2\) 我们可以用 if 判断大小。如果 \(r =1\) 就没有逆序对。时间复杂度 \(O(1)\)。

当前得分:\(1+5=6\)

Part 3

特殊性质 B 说,这 \(10\) 分是为了 \(O(qn^2)\) 而设,考虑写出 \(O(qn^2)\) 的代码。能不能尝试每次询问都求一遍逆序对呢?可以,先用 \(O(n^2)\) 枚举出所有下标对,再判值的大小就可以了,套上 \(q\) 次询问,就是 \(O(qn^2)\)。

当前得分:\(1+5+10=16\)

Part 4

第四档数据有一个特点——\(r \le 2\)。

从 Part 2 的分析中可知,

如果 \(r=2\) 我们可以用 if 判断大小。如果 \(r =1\) 就没有逆序对。

单次询问时间复杂度 \(O(1)\),总体时间复杂度是 \(O(q)\)。

当前得分:\(1+5+10+13=29\)

Part 5

尝试优化 Part 3 中的暴力。

模拟程序运行,发现瓶颈在于,同样的数列,逆序对算了很多次!这意味着有许多重复运算,浪费时间。既然数列不会变,我们可以考虑使用离线思想进行预处理。用一个数组记录每一个下标 \(i(1 \le i \le n)\) 作为每个逆序对的第二个下标时的逆序对数量,前缀和用于查询。

很好,时间复杂度从 \(O(qn^2)\) 降到了 \(O(q+n^2)\),可以通过第五档数据。

结果你惊奇地发现,最后一档也过了!实测确实可以 AC,可能有略微卡常。洛谷评测机实力强劲!

不错,你已经从骗分到暴力、再到 AC 了。当然如果没有 AC 也有八九十分。这要看出题人的想法。

我们要学习的是,先打暴力,然后再优化暴力。甚至可以 AC。

Part 6

Part 5 的实现最多可以跑到 \(4 \times 10^8\)。如果我们要稳过的话,要学习更高级的算法。

6.1 树状数组

为了解决这个问题,我们可以用树状数组求逆序对。

for(int i=n; i>=1; i--){
	ans+=Q(a[i]-1);
	Add(a[i]);
}

(上核心代码用于求出一个序列的逆序对数量)

在普通逆序对的基础上,使出同样的思路——一个数组记录每一个下标 \(i(1 \le i \le n)\) 作为每个逆序对的第二个下标时的逆序对数量。用前缀和 \(O(1)\) 解决查询,时间复杂度 \(O(n \log n+q)\)。可以稳过。

6.2 莫队

本题重在不修改的查询,可以使用莫队。

Part 7

为什么可以这么做?因为 \(l\) 恒为 \(1\)。

其实本题还有一个加强版,洛谷 P5047,一般使用 二次离线莫队 等解决。

Part 8

在考场上,如果一道题做不出来就建议骗分,蚂蚁腿也是肉。最后说一遍,我们要学习的是,先打暴力,然后再优化暴力。甚至可以 AC。

posted @ 2026-07-13 11:29  lz5332t  阅读(17)  评论(0)    收藏  举报