题目
题目来源:CCF 上海省选 2001;
在线评测地址:Luogu#2527;
运行限制:时间 (1.00 extrm{s}),空间 (128 extrm{MiB})。
题目描述
Panda 是个数学怪人,他非常喜欢研究跟别人相反的事情。最近他正在研究筛法,众所周知,对一个范围内的整数,经过筛法处理以后,剩下的全部都是质数,不过 Panda 对这些不感兴趣,他只对被筛掉的数感兴趣,他觉得在这些被筛掉的数中一定隐藏着重要的宇宙秘密,只是人们还没有发现罢了。
Panda 还觉得如果只是单纯地从小到大筛的话,还不足够发现其中的奥秘,于是他决定对至多只包含某些质因数的数进行研究(比如说至多只包含质因数 (2)、(3) 的数有 (2,3,4,6,8,9,cdots)),他需要得到这些数中第 (k) 小的数((k) 是 Panda 认为的宇宙系数),请你编个程序,帮助他找到这个数。
输入格式
第一行有两个数 (n)、(k),(n) 代表质因数的个数,(k) 代表那个宇宙系数。
第二行有 (n) 个数,代表这 (n) 个质因数。
输出格式
仅 (1) 行,即至多只包含这 (n) 个质因数的数中第 (k) 小的数。
数据规模及约定
(1le nle 100),(1le kle 10^5)。
保证所有质因数小于 (1000) 且互不相同,保证答案 (le 2 imes 10^9)。
分析
题意是说给定一系列质数 (p_1,p_2,cdots,p_n),求出所有 (large{t=prodlimits_{i=1}^n p_i^{a_i} (a_iinmathbb{N}\,land\, prod a_i>0)}) 中第 (k) 大的。
这是一道比较数学的题,要优化算法的关键是利用好性质。
一个 · 伪正解
一个极其暴力的做法是枚举 (a_i),然后排序。这必然无法通过,因为有很多性质没有被很好地利用。
首先,我们可以想到一个很显然的性质:一个数必然可以通过一个已经被统计过的数转移而来(即乘上任意一个质数),当用了最大的质数时,转移就是唯一的。
接下来,我们可以在转移上做文章。显然,用小的数转移的数也会比较小,所以我们可以把所有的数都丢到优先队列里面去,每一次取最小的数进行转移。复杂度 (mathcal{O}(nklog k)) 很危险。
根据数据规模,这个做法似乎是未曾设想的道路。那么正解怎么做呢?我们就要更加充分地利用性质。
Solution
首先我们已经注意到了,同一个数用小的质数转移得到的数就小。同理,同一个质数用小的数转移得到的数也小。
这样,我们每次枚举质数,标记每一个质数转移到的位置 (d_i),求出 (min p_i t_{d_i}),并赋值给下一个 (t),然后 (d_ileftarrow d_i+1)。
易证这样的 (t) 必然是满足要求的。证明如下:
如果存在一个介于上一个 (t) 和这一个 (t) 的 (t^prime),根据定义,这个 (t^prime) 必然从一个数转移((t_0^prime))和乘上一个质数 (p_0^prime)。
考虑 (p_0) 对应的 (d_0),根据 (t^prime) 的定义,可以得到 (t_{d_0}> t_0^prime)。
但是根据 (d_0) 的定义,这样的 (t_0^prime) 必然已经被转移了,所以不存在这样的 (t^prime),证毕。
这样就可以在 (mathcal{O}(nk)) 的时间内完成了。如果加上优先队列优化就可以达到 (mathcal{O}(klog n))。
但是有一点需要注意,那就是这样得到的数可能会与当前的最大值重复,注意判断一下去个重。
Code
友善提醒一下,这道题中的那个 (2 imes 10^9) 是假的,所以别忘了开 long long
。
#include <cstdio>
using namespace std;
typedef long long ll;
const int max_n = 100, max_k = 100000;
ll ans[max_k+1], a[max_n], pos[max_n] = {}; // 数组不解释
int main()
{
int n, k, mn_pos;
ll mn;
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i++)
scanf("%lld", a + i); // 输入
ans[0] = 1;
for (int i = 1; i <= k; i++)
{
mn = 0x3f3f3f3f3f3fll, mn_pos = -1;
for (int j = 0; j < n; j++)
if (mn > a[j] * ans[pos[j]])
mn = a[j] * ans[pos[j]], mn_pos = j; // 统计最小值
if (mn != ans[i-1]) // 不重复就更新
ans[i] = mn;
else
i--;
pos[mn_pos]++; // 指向下一位
}
printf("%lld
", ans[k]);
return 0;
}