[蓝桥杯]三元组中心问题
三元组中心问题
题目描述
在数列 a1,a2,⋯,ana1,a2,⋯,an 中,如果对于下标 i,j,ki,j,k 满足 0<i<j<k<n+10<i<j<k<n+1 且 ai<aj<akai<aj<ak,则称 ai,aj,akai,aj,ak 为一组递增三元组,ajaj 为递增三元组的中心。
给定一个数列,请问数列中有多少个元素可能是递增三元组的中心。
输入描述
输入的第一行包含一个整数 nn。
第二行包含 nn 个整数 a1,a2,⋯,ana1,a2,⋯,an,相邻的整数间用空格分隔,表示给定的数列。
其中,2≤n≤1000,0≤数列中的数≤100002≤n≤1000,0≤数列中的数≤10000。
输出描述
输出一行包含一个整数,表示答案。
输入输出样例
示例
输入
5
1 2 5 3 5
输出
2
样例说明:
a2a2 和 a4a4 可能是三元组的中心。
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
总通过次数: 3438 | 总提交次数: 3870 | 通过率: 88.8%
难度: 困难 标签: 2020, 模拟, 思维, 省模拟赛
三元组中心问题算法详解
问题描述
给定一个数列,统计有多少个元素可以作为递增三元组的中心,即存在下标 i, j, k
满足 0 < i < j < k < n+1
且 a_i < a_j < a_k
。
算法思路
核心思想
对于每个位置 j
,检查其左侧是否存在小于 a[j]
的元素,右侧是否存在大于 a[j]
的元素。若两者同时满足,则 a[j]
可作为三元组的中心。
暴力法(推荐)
- 时间复杂度:O(n²),适用于 n ≤ 1000(1000² = 1e6 次操作,在 1 秒内可完成)。
- 空间复杂度:O(1),无需额外空间。
- 步骤:
- 遍历每个可能中心位置
j
(下标 1 到 n-2)。 - 向左扫描检查是否存在
a[i] < a[j]
。 - 向右扫描检查是否存在
a[k] > a[j]
。 - 若两者均满足,则计数器加 1。
- 遍历每个可能中心位置
预处理法(优化)
- 时间复杂度:O(n),通过预处理避免嵌套循环。
- 空间复杂度:O(n),需两个辅助数组。
- 步骤:
- 预处理
left_min
:left_min[j]
表示a[0..j-1]
的最小值。 - 预处理
right_max
:right_max[j]
表示a[j+1..n-1]
的最大值。 - 若
left_min[j] < a[j] < right_max[j]
,则j
是中心。
- 预处理
代码实现(暴力法)
#include <iostream>
using namespace std;const int MAX_N = 1005;
int a[MAX_N];int main() {int n;cin >> n;for (int i = 0; i < n; i++) {cin >> a[i];}int count = 0;// 中心位置 j 的范围:下标 1 到 n-2for (int j = 1; j <= n-2; j++) {bool left_flag = false;// 向左扫描:找是否存在 a[i] < a[j]for (int i = 0; i < j; i++) {if (a[i] < a[j]) {left_flag = true;break; // 找到即退出}}if (!left_flag) continue; // 左侧无更小数则跳过bool right_flag = false;// 向右扫描:找是否存在 a[k] > a[j]for (int k = j+1; k < n; k++) {if (a[k] > a[j]) {right_flag = true;break; // 找到即退出}}if (left_flag && right_flag) {count++;}}cout << count << endl;return 0;
}
代码解析
- 输入处理:读取数列长度
n
和数列a
。 - 中心位置遍历:
- 下标
j
从 1 到 n-2(因首尾不能作为中心)。
- 下标
- 左侧检查:
- 从
j-1
向左扫描至 0,若有a[i] < a[j]
则标记left_flag
。
- 从
- 右侧检查:
- 若左侧满足,再从
j+1
向右扫描至末尾,若有a[k] > a[j]
则标记right_flag
。
- 若左侧满足,再从
- 计数条件:
left_flag
和right_flag
同时为真时计数。
实例验证
- 输入:
n=5, a = {1, 2, 5, 3, 5}
- 验证过程:
j
a[j]
左侧检查 右侧检查 是否计数 1 2 1<2 → 满足 5>2 → 满足 ✓ 2 5 1<5 → 满足 3<5, 5=5 → 不 ✗ 3 3 1<3,2<3 → 满足 5>3 → 满足 ✓ - 输出:2(与样例一致)
注意事项
- 边界处理:
- 当
n < 3
时直接输出 0(无法构成三元组)。 - 中心位置
j
的范围严格限定为[1, n-2]
(下标从 0 开始)。
- 当
- 严格递增:
- 必须满足
a[i] < a[j] < a[k]
,相等不成立。
- 必须满足
- 性能保障:
- 最坏情况(如完全逆序数列)仍可在 1 秒内处理 n=1000。
优化建议
测试用例设计
测试用例 | 预期输出 | 验证点 |
---|---|---|
n=2, [1,2] | 0 | 最小长度边界 |
n=3, [3,2,1] | 0 | 完全逆序数列 |
n=4, [1,1,1,1] | 0 | 全相等 |
n=5, [5,4,3,2,1] | 0 | 严格递减 |
n=5, [1,3,2,4,5] | 1 | 中心在位置1(值3) |
n=6, [1,2,3,4,5,6] | 4 | 完全递增时的中心数量 |
-
预处理法:
#include <iostream> #include <algorithm> using namespace std;const int MAX_N = 1005; const int INF = 10001; // 大于数据最大值(10000) int a[MAX_N], left_min[MAX_N], right_max[MAX_N];int main() {int n;cin >> n;for (int i = 0; i < n; i++) cin >> a[i];// 初始化边界left_min[0] = INF; // 位置0无左侧元素right_max[n-1] = 0; // 位置n-1无右侧元素// 预处理左侧最小值for (int i = 1; i < n; i++) {left_min[i] = min(left_min[i-1], a[i-1]);}// 预处理右侧最大值for (int i = n-2; i >= 0; i--) {right_max[i] = max(right_max[i+1], a[i+1]);}int count = 0;for (int j = 1; j <= n-2; j++) {if (left_min[j] < a[j] && a[j] < right_max[j]) {count++;}}cout << count << endl;return 0; }
-
优势:时间复杂度降至 O(n),适合更大规模数据(如 n=10⁶)。
-
二分优化(扩展):
- 若数据规模扩大(如 n=10⁵),可对左右侧检查使用二分查找,将暴力扫描的 O(n) 降至 O(log n)