【题解】[SHOI2001] Panda 的烦恼

题目

题目来源: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;
}

非常感谢您读完此文章。

如果您喜欢这篇文章,请点一个赞支持一下博主。

如果您对此文有意见,欢迎在评论区留下你的看法,5ab 会尽力达到。

如果您真的不喜欢这篇文章,也请不要喷,5ab 欢迎私信反馈意见。

原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-shoi2001_lg2527.html