题目描述
给定一个最多包含1 000 0001\,000\,0001000000个数字的列表,顺序任意,求该列表按非降序排序后第iii个元素的值。为了避免输入数据量过大导致I/O\texttt{I/O}I/O成为性能瓶颈,题目要求参赛者根据给定的三种列表命令动态生成数据。
三种列表命令如下:
NList\texttt{NList}NList:普通列表。格式为NList(n,a1,a2,…,an)\texttt{NList}(n, a_1, a_2, \dots, a_n)NList(n,a1,a2,…,an),表示直接给出nnn个元素的值。
IList\texttt{IList}IList:递增列表。格式为IList(n,s,i)\texttt{IList}(n, s, i)IList(n,s,i),表示生成nnn个元素,首项为sss,公差为iii(iii可为正、负或零),即第kkk个元素为s+(k−1)⋅is + (k-1) \cdot is+(k−1)⋅i。
RList\texttt{RList}RList:随机列表。格式为RList(n,l,h,s)\texttt{RList}(n, l, h, s)RList(n,l,h,s),表示生成nnn个随机数,范围在[l,h][l, h][l,h]之间,使用给定的种子sss和如下随机数生成器:
- 每次迭代:seed←(seed×17+11) mod 232\textit{seed} \gets (\textit{seed} \times 17 + 11) \bmod 2^{32}seed←(seed×17+11)mod232;
- 生成的随机数为l+(seed mod (h−l+1))l + (\textit{seed} \bmod (h - l + 1))l+(seedmod(h−l+1))。
多个列表命令可以通过+连接,生成一个拼接后的完整列表。所有生成的数字均为323232位有符号整数。输入保证总数字个数在111到1 000 0001\,000\,0001000000之间,且每行命令数不超过323232,行长度不超过100010001000字符。
输入格式
输入包含多个测试用例,每个用例占两行:
- 第一行一个整数iii(1≤i≤1 \le i \le1≤i≤列表总长度),表示排序后要找的第iii个元素(索引从111开始)。
- 第二行一个字符串,由若干个列表命令通过
+连接而成,不含空格。
输入以单独一行0结束。
输出格式
对于每个测试用例,输出一行,格式为Case X: Y,其中XXX为用例编号(从111开始),YYY为所求的第iii个元素。
样例
输入
4 NList(10,-9,6,-4,-3,6,501,7,6,6,-10000) 13 IList(5,0,1)+IList(3,6,0)+IList(5,5,-1) 1 IList(1000000,-90,0) 123456 RList(1000000,-2000000000,2000000000,0) 200000 IList(50001,-25000,1)+RList(500000,-25000,25000,3333333333)+NList(4,0,0,0,0) 0输出
Case 1: -3 Case 2: 6 Case 3: -90 Case 4: -1735543272 Case 5: -6808题目分析
本题的核心挑战在于高效地找到大规模无序数组中的第iii小元素。直接生成所有数字并完整排序的时间复杂度为O(nlogn)O(n \log n)O(nlogn),对于n=106n = 10^6n=106在时限内通常是可行的,但题目特别提示需要“高效的算法”,因此更优的选择是使用快速选择算法。
快速选择(nth_element\texttt{nth\_element}nth_element)是快速排序的变体,能够在期望线性时间O(n)O(n)O(n)内找到第iii小的元素,而无需对全数组排序。C++\texttt{C++}C++标准库中的std::nth_element正是这一算法的实现,它部分重排数组,使得第iii个位置上的元素就是排序后该位置应有的元素,且其左侧元素都不大于它,右侧元素都不小于它。
另一个需要关注的点是数据生成。由于输入以字符串形式给出,且命令数不超过323232,总长度不超过100010001000字符,我们可以直接解析每个命令并实时生成数字存入数组。解析时需注意:
- 命令名(
NList、IList、RList)和参数之间没有空格; - 参数用逗号分隔;
- 生成随机数时必须使用646464位整数(如
long long),防止乘法溢出,同时取模时范围h - l + 1可能超过323232位有符号范围,也需用646464位处理。
解题思路
步骤一:解析输入命令
对于每个测试用例,读入索引iii和一行命令字符串。由于命令间用+分隔,我们按+分割得到每个独立的命令子串。
对每个命令子串:
- 找到左括号
(和右括号)的位置; - 提取括号内的参数部分;
- 将参数中的逗号
,替换为空格,方便使用stringstream读取; - 根据命令前缀(
NList、IList、RList)分别处理。
步骤二:生成数字
- NList\texttt{NList}NList:第一个参数为nnn,随后读取nnn个整数,依次存入数组。
- IList\texttt{IList}IList:读取nnn、sss、iii,循环nnn次,每次计算s+k⋅is + k \cdot is+k⋅i并存入数组。注意kkk可能很大(最多10610^6106),但乘积仍可用646464位安全计算。
- RList\texttt{RList}RList:读取nnn、lll、hhh、sss。种子sss是323232位无符号整数,但输入可能以十进制给出,应读入为
unsigned long long再截断为unsigned int。每次迭代:seed = (seed * 17 + 11) & 0xFFFFFFFFULL;- 生成值
l + (seed % (h - l + 1)),由于h - l + 1可能超过int范围,需用long long计算。
步骤三:查找第iii小元素
将所有生成的数字存入vector<int>后,调用nth_element(nums.begin(), nums.begin() + i - 1, nums.end()),此时nums[i-1]即为答案。该函数的时间复杂度为线性期望。
复杂度分析
- 时间复杂度:解析和生成数字O(n)O(n)O(n),快速选择O(n)O(n)O(n)期望,总复杂度O(n)O(n)O(n)。
- 空间复杂度:存储所有数字O(n)O(n)O(n),n≤106n \le 10^6n≤106,内存可接受。
代码实现
// Troubles for Modern Days Problemsetters// UVa ID: 11124// Verdict: Accepted// Submission Date: 2026-06-21// UVa Run Time: 0.100s//// 版权所有(C)2026,邱秋。metaphysis # yeah dot net#include<bits/stdc++.h>usingnamespacestd;// 解析一行命令,生成所有数字并追加到 nums 中voidparseAndGenerate(conststring&line,vector<int>&nums){size_t pos=0;while(pos<line.size()){size_t plus=line.find('+',pos);// 查找命令分隔符string cmd=line.substr(pos,plus-pos);// 提取单个命令size_t lp=cmd.find('('),rp=cmd.find(')',lp);string args=cmd.substr(lp+1,rp-lp-1);// 括号内的参数列表for(char&c:args)if(c==',')c=' ';// 逗号转空格便于流读取stringstreamss(args);if(cmd.find("NList")==0){intn;ss>>n;for(intk=0;k<n;++k){intval;ss>>val;nums.push_back(val);}}elseif(cmd.find("IList")==0){intn;longlongs,inc;ss>>n>>s>>inc;for(intk=0;k<n;++k)nums.push_back((int)(s+inc*k));}elseif(cmd.find("RList")==0){intn;longlongl,h;unsignedlonglongseed;ss>>n>>l>>h>>seed;unsignedintseed32=(unsignedint)(seed&0xFFFFFFFFULL);longlongrange=h-l+1;// 可能大于 2^31,用 long longfor(intk=0;k<n;++k){seed32=(unsignedint)((seed32*17ULL+11ULL)&0xFFFFFFFFULL);nums.push_back((int)(l+(seed32%range)));}}pos=(plus==string::npos)?line.size():plus+1;}}intmain(){ios::sync_with_stdio(false);cin.tie(nullptr);inti,caseNo=1;string line;while(cin>>i){if(i==0)break;getline(cin,line);// 消耗第一行末尾的换行符getline(cin,line);// 读取实际的命令行vector<int>nums;nums.reserve(1000000);parseAndGenerate(line,nums);nth_element(nums.begin(),nums.begin()+i-1,nums.end());cout<<"Case "<<caseNo++<<": "<<nums[i-1]<<"\n";}return0;}总结
本题巧妙地将“大输入生成”与“选择算法”结合起来,考察了两个关键能力:
- 字符串解析与数据生成:需要正确处理三种不同格式的命令,尤其注意随机数生成时的溢出和取模范围;
- 高效选择算法:使用
nth_element替代完整排序,在期望线性时间内求解,是处理大规模“第kkk小”问题的经典策略。
此外,本题也提醒我们,在竞赛环境中,合理的算法选择(而非盲目排序)往往能显著提升程序性能,而C++\texttt{C++}C++标准库中的高效算法(如nth_element)是实现这一目标的利器。