【题解】[SHOI2001] Panda 的烦恼

题目

题目来源:CCF 上海省选 2001;

在线评测地址:Luogu#2527

运行限制:时间 \(1.00\ \textrm{s}\),空间 \(128\ \textrm{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\) 小的数。

数据规模及约定

\(1\le n\le 100\)\(1\le k\le 10^5\)

保证所有质因数小于 \(1000\) 且互不相同,保证答案 \(\le 2\times 10^9\)

分析

题意是说给定一系列质数 \(p_1,p_2,\cdots,p_n\),求出所有 \(\large{t=\prod\limits_{i=1}^n p_i^{a_i}\ (a_i\in\mathbb{N}\,\land\, \prod a_i>0)}\) 中第 \(k\) 大的。

这是一道比较数学的题,要优化算法的关键是利用好性质。

一个 · 伪正解

一个极其暴力的做法是枚举 \(a_i\),然后排序。这必然无法通过,因为有很多性质没有被很好地利用。

首先,我们可以想到一个很显然的性质:一个数必然可以通过一个已经被统计过的数转移而来(即乘上任意一个质数),当用了最大的质数时,转移就是唯一的。

接下来,我们可以在转移上做文章。显然,用小的数转移的数也会比较小,所以我们可以把所有的数都丢到优先队列里面去,每一次取最小的数进行转移。复杂度 \(\mathcal{O}(nk\log k)\) 很危险。

根据数据规模,这个做法似乎是未曾设想的道路。那么正解怎么做呢?我们就要更加充分地利用性质。

Solution

首先我们已经注意到了,同一个数用小的质数转移得到的数就小。同理,同一个质数用小的数转移得到的数也小。

这样,我们每次枚举质数,标记每一个质数转移到的位置 \(d_i\),求出 \(\min p_i t_{d_i}\),并赋值给下一个 \(t\),然后 \(d_i\leftarrow 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}(k\log n)\)

但是有一点需要注意,那就是这样得到的数可能会与当前的最大值重复,注意判断一下去个重。

Code

友善提醒一下,这道题中的那个 \(2\times 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\n", ans[k]);

	return 0;
}
posted @ 2020-08-25 15:12  5ab  阅读(99)  评论(0)    收藏  举报