0.题目大意
给定三个正整数m,n,k(2≤m≤1e7,1≤k≤n≤5e7)。已知一个长度为n的序列a满足(mod为题目给出的常数,为1e9+7),求a中第k小的数字。
(注意,本序列从开始,即
)
1.分析题目
①初步分析
首先,开一个大小为5e7的整形数组再排序是不现实的。题目的内存上限是313072KB,而一个大小为5e7的整形数组所占用的内存为195312.5KB。显然,MLE了。
同时,n的上限是5e7,而时限为1.5s。这说明什么?这说明只能用O(n)的算法通过。甚至连常数打了都不行。虽然题目的标签有一个基数排序,但是实际上这道题不能直接使用基排。(你想想,不仅不能使用数组存储数字;而且基排的常数是比较大的,不能保证卡过时限)
②打开思路
那能怎么办?如果你的思路是比较开阔的,你应该想到一道类似的题目:给出一个数组,求第k小的数字。那题数据很小,所以使用各种方法都能过。而其中最正统的写法就是运用了快排的思想:
每次选择一个“哨兵”,大于它的数字排在它左边,小于它的数字在它的右边。计算两边的元素个数,可以缩小第k小数字的范围。
但是,这个算法有一个缺点:其最优的时间复杂度为O(n),可是但凡你的运气差一点,就很可能导致两边的数字个数相差甚远。比如,你运气奇差或者题目数据有些变态,导致你每次选中的哨兵都是当前序列中的最大或最小值,那么该算法就会退化至。这是不能接受的。
所以,我们要使用另一种思路:因为这些数字的值域都很小,所以我们大可以使用一个bool数组来记录数字i是否在序列中出现过。再求一个前缀和,这题目就能轻松搞定了。
这个思路是可以借鉴的。但是在本题中, 的理论最大值为mod-1,即1e9+6。这个数字还是太大了——5e7的数组都存不下,更何况1e9呢!思路进入了死胡同。
但是,这个“死胡同”中,并非无路可走:既然1e9的数组存不下,那我就把数字分为两部分——低五位(即个位、十位、百位、千位、万位)与高五位(十万、百万、千万、亿、十亿)。只要我们能求出第k小数字的高五位与低五位,问题就能得到解决。因为高五位的权值更大,所以我们先处理高五位,求出第k小的数字的高五位是多少。再求低五位,就能轻松搞定了。
2.最终代码
①Q:但是,我们怎么求每个数字的高五位与低五位呢?
A:(精华在于注释,下同)
const int mod = 1e9 + 7, num = 1e5;
int m, n, k;
int cnt[num + 1];
cin>>m>>n>>k;
int last = 1, x, h; //h是前面5位
cnt[last/num+1]++;
for (int i = 2; i <= n; i++) {
long long now=1ll*x*last
now%=mod;
x=now;
cnt[x / num + 1]++; //注意,这里有一个加一,为了方便cnt[0]的处理
last = x;
}
②Q:我们怎么存储个数呢?
A:
int cnt[num + 10];//cnt[i]表示值为i的个数出现的次数
③Q:我们怎么确定第k小数字的前五位呢?
A:
for (int i = 1; i <= num; i++) {
cnt[i] += cnt[i - 1];
if (cnt[i] >= k) {
h = i - 1;//h表示第k小数字的高五位。-1是因为处理cnt[0]时会数组越界,就整体都加1
k -= cnt[i - 1];//这一步不能忘。因为第一轮筛选过后k对于高五位为i的数字内部来说k应该减去cnt[i-1]
break;
}
}
④ Q:我悟了……
A:悟了就好,自己写完之后来核对一下代码或者自己直接交。
#include<iostream>//有缘千里来相见,万年不变头文件
#include<stdio.h>
#define int long long//十年OI一场空,不开long long见祖宗
using namespace std;
const int mod = 1e9 + 7, num = 1e5;//酒逢知己千杯少,定义常量莫忘了
int m, n, k;
int cnt[num + 1];
signed main() {
scanf("%lld%lld%lld",&m,&n,&k);
int last = 1, x, h;
cnt[last/num+1]++;
for (int i = 2; i <= n; i++) {
x = last * m % mod;//这里开了long long,就不用担心爆int了
cnt[x / num + 1]++;
last = x;
}
for (int i = 1; i <= num; i++) {
cnt[i] += cnt[i - 1];
if (cnt[i] >= k) {
h = i - 1;
k -= cnt[i - 1];
break;
}
}
// printf("h:%lld k:%lld\n",h,k);
for (int i = 0; i <= num; i++) {
cnt[i] = 0;
}
last = 1;
if(last/num==h) cnt[last%num+1]++;
for (int i = 2; i <= n; i++) {
x = last * m % mod;
if(x/num==h)
cnt[x%num+1]++;
last = x;
}
for(int i=1;i<=num;i++){
cnt[i]+=cnt[i-1];
if(cnt[i]>=k){
int answer=h*num+i-1;
printf("%lld",answer);
return 0;
}
}
}
完结撒花!