• 【题解】[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 欢迎私信反馈意见。

  • 相关阅读:
    商品尺码规格和颜色需要支持双引号
    php xss 函数
    yar 调用rpc方法
    【转】Linux常用命令大全
    【Netty】最透彻的Netty原理架构解析
    【Git项目管理】分布式 Git
    【SpringBoot】几种定时任务的实现方式
    【Elasticsearch-Java】Java客户端搭建
    【Git项目管理】Git分支
    【Git项目管理】git新手入门——基础教程
  • 原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-shoi2001_lg2527.html
Copyright © 2020-2023  润新知