问题描述
20232023 年 1313 月 32∼3332∼33 日,福建省第十一届大学生程序设计竞赛在东南大学如期举办。来自厦门大学、福州大学、集美大学、华侨大学、福建师范大学等 100100 所高校的 nn 支队伍参赛。
- 华侨大学“红日初升”代表队,以 1313 题 960960 罚时,夺得冠军(第 11 名);
- 厦门大学“点分治分点”代表队、福州大学“西南饺子馆”代表队, 1212 题 950950 罚时,以一模一样的罚时和题数,并列亚军(第 22 名);
- 福建师范大学“夜幕降临”代表队、福建理工大学“福理重磅出击”代表队、福建农林大学“重生之我是懒狗”代表队, 1111 题 13101310 罚时,以一模一样的罚时和题数,并列殿军(第 44 名);
- ……
- 集美大学“抱歉,这没有集美”代表队, 999999 发代码提交却没有 11 发通过,最后以 00 题 00 罚时的成绩,惨遭爆零,取得全场最后一名。
前线记者贝贝发来报道。
已知所有队伍的通过题数、罚时,并且保证给出的顺序,符合 ACM 比赛最终榜单:按题数降序排列,当题数相同时,按照罚时升序排序。
贝贝想知道,若将相同题数、相同罚时的队伍归为一类,最终共有多少类队伍?
输入格式
第一行包含一个整数 n(1≤n≤105)n(1≤n≤105) ,表示队伍的数量。
接下来的 nn 行,每行 22 个整数 ai,bi(0≤ai≤13,0≤bi≤2×103)ai,bi(0≤ai≤13,0≤bi≤2×103) ,分表表示第 ii 个队伍的通过题数和罚时。
保证给出的顺序符合 ACM 比赛的最终榜单。
输出格式
输出仅一行,包含一个整数,表示队伍的种类数量。
样例输入
7
13 960
12 950
12 950
11 1310
11 1310
11 1310
0 0
样例输出
4
说明
在样例中,共有 44 类队伍:
- 1313 题 960960 罚时;
- 1212 题 950950 罚时;
- 1111 题 13101310 罚时;
- 00 题 00 罚时。
运行限制
语言 | 最大运行时间 | 最大运行内存 |
---|---|---|
C++ | 1s | 256M |
C | 1s | 256M |
Java | 2s | 256M |
Python3 | 3s | 256M |
PyPy3 | 3s | 256M |
Go | 3s | 256M |
JavaScript | 3s | 256M |
总通过次数: 1244 | 总提交次数: 1459 | 通过率: 85.3%
难度: 困难 标签: 双指针, 计数问题
算法思路
本问题要求统计 ACM 竞赛队伍中不同题数罚时组合的种类数量。输入数据已按题数降序、罚时升序排序,因此我们只需遍历一次队伍列表,通过比较相邻队伍的题数和罚时差异来计数类别。算法核心是 相邻比较法,时间复杂度 O(n),空间复杂度 O(1)。
算法演示
graph TD
A[开始] --> B[读取第一支队伍数据]
B --> C[初始化计数器 ans=1]
C --> D[遍历后续队伍]
D --> E{当前队伍与上一支队伍<br>题数或罚时不同?}
E -- 是 --> F[ans++]
F --> G[更新上一支队伍数据]
E -- 否 --> G
G --> D
D --> H[输出 ans]
H --> I[结束]
完整代码(C++)
#include <cstdio>
int main() {
int n;
scanf("%d", &n);
if (n == 0) {
printf("0\n");
return 0;
}
int prev_a, prev_b;
scanf("%d %d", &prev_a, &prev_b);
int ans = 1;
for (int i = 1; i < n; i++) {
int curr_a, curr_b;
scanf("%d %d", &curr_a, &curr_b);
if (curr_a != prev_a || curr_b != prev_b) {
ans++;
}
prev_a = curr_a;
prev_b = curr_b;
}
printf("%d\n", ans);
return 0;
}
代码解析
-
输入处理:
- 使用
scanf
高效读取数据(比cin
快 2-3 倍)1
- 处理边界情况:
n=0
时直接返回 0
- 使用
-
核心逻辑:
if (curr_a != prev_a || curr_b != prev_b) ans++;
- 题数不同:必然是新类别(如 13 题 → 12 题)
- 题数相同但罚时不同:也是新类别(如 12 题/950 罚时 → 12 题/1310 罚时)
- 完全相同:跳过计数(如连续两支 12 题/950 罚时)
-
数据更新:
prev_a = curr_a; prev_b = curr_b;
- 无论是否新类别,都更新参考值为当前队伍,确保后续正确比较
实例验证
以样例输入为例:
7
13 960 // [1] ans=1(初始化)
12 950 // [2] 题数不同 → ans=2
12 950 // [3] 完全相同 → 跳过
11 1310 // [4] 题数不同 → ans=3
11 1310 // [5] 完全相同 → 跳过
11 1310 // [6] 完全相同 → 跳过
0 0 // [7] 题数不同 → ans=4
最终输出 4
,符合预期。
注意事项
-
输入顺序依赖:
- 题目已保证输入按题数降序、罚时升序排序
- 若未排序需先排序(时间复杂度 O(n log n))
-
数据范围:
n ≤ 10⁵
:必须用 O(n) 算法,暴力比对会超时罚时 ≤ 2000
:int
存储足够,无需long
-
边界情况:
- 单支队伍(
n=1
):直接返回 1 - 全相同队伍:返回 1
- 全不同队伍:返回
n
- 单支队伍(
测试点设计
测试类型 | 输入数据 | 预期输出 | 验证目标 |
---|---|---|---|
最小规模 | n=1 :[5,100] | 1 | 边界处理 |
全相同队伍 | n=3 :[10,200],[10,200],[10,200] | 1 | 相同类别识别 |
全不同队伍 | n=3 :[13,100],[12,200],[11,300] | 3 | 差异识别 |
混合情况 | 样例输入 | 4 | 综合逻辑验证 |
最大规模 | n=10⁵ 随机数据 | 通过 | 性能验证(<100ms) |
优化建议
-
输入输出加速:
ios::sync_with_stdio(false); cin.tie(0);
- 使
cin
速度接近scanf
(需包含<iostream>
)
- 使
-
避免存储全量数据:
- 当前算法仅存储上一支队伍数据,空间 O(1)
- 存储全部数据会导致 O(n) 空间,不必要
-
并行化优化:
#pragma omp parallel for reduction(+:ans)
- 对超大规模数据(如 n>10⁷)可用 OpenMP 并行处理
- 需编译选项
-fopenmp