题解 P1574 超级数


解题思路:

超级数什么的,其实就是反素数,这两者的定义是一模一样的,所以下面直接介绍反素数。

首先,大家应该都知道素因子分解(看到紫题还点进来的一般都会吧 Q w Q QwQ QwQ )。即: n u m = p 1 k 1 × p 2 k 2 × p 3 k 3 × . . . × p n k n num=p1^{k1}\times p2^{k2} \times p3^{k3}\times ... \times pn^{kn} num=p1k1×p2k2×p3k3×...×pnkn 其中 p 是素数, k 是指数。

那么考虑一般的求反素数方法,就是对于一个范围内数,对其进行素因子分解,然后与 ans 来比较。

显而易见,这种算法效率很低。究其本质,其实是有很多数的因数很少,这种算法却无法预知这种数,而不得不一点一点的排查。

由此,我们可以想到主动构造反素数。


首先给出一条结论:

所有的反素数在素因子分解后 k 数组是不上升的(p数组递增的情况下)。

这个可以用反证法证明:

假设有这样一个反素数 num 其素因子分解后有 k i < k j k_i < k_j ki<kj 且 $ p_i < p_j $ ,那么,如果将 k i k_i ki k j k_j kj 这两者交换一下,很明显新的数要比原来的小,同时两者因数个数相等,都等于 ( k 1 + 1 ) × ( k 2 + 1 ) × ( k 3 + 1 ) × . . . × ( k n + 1 ) (k_1+1)\times (k_2+1)\times(k_3+1)\times ... \times(k_n+1) (k1+1)×(k2+1)×(k3+1)×...×(kn+1) ,而根据反素数的定义,反素数的因数个数是比其小的所有数中最大的(不包含等于),这与反素数的定义矛盾,所以 num 一定不是反素数。命题得证。

有这个结论可以很容易推出反素数一定是从最小素数2开始的连续素数的幂次的乘积。

所以说,我们可以枚举反素数在素因数分解后的素数个数,然后枚举每个素数的幂次,构造反出素数。(其实就是个 dfs )

代码如下:

#include<cstdio>
using namespace std;
int pri[30]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47};
//乘积等于614889782588491410恰好比1e17大一点 
//乘上下一个素数53就是32589158477190044730,有点浪费了 
long long ans_num,ans_tot,n,T;
void dfs(int now,long long num,long long tot,long long top){
	//   当前素数     当前数值     当前因数数     能取到的最大i
	if(num>n||now>=15)return;//15,数组从0开始 
	if(tot>ans_tot){ans_tot=tot;ans_num=num;}
	if(tot==ans_tot&&ans_num>num){ans_num=num;}
	//优先因数个数,其次保证最小
	for(int i=1;i<=top;i++){
		if(num*pri[now]>n)break;
		num*=pri[now];
		dfs(now+1,num,tot*(i+1),i);
	}
}
int main(){
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		ans_num=0;ans_tot=0;
		dfs(0,1,1,57);
		//2^57=1.44115188075855872e+17,恰好比1e17大一点 
		printf("%lld\n",ans_num);
	}
	return 0;
}

然后就 TLE 了两个点。

重新读题,发现其实算反素数效率已经很高了,但架不住 m ≤ 1 0 5 m\leq 10^5 m105 的数据范围。

然后我们就想到可以把反素数全都算出来再应付询问。这里可以打表。也可以算出一个反素数后再算从一到比这个反素数小一的反素数。效率都挺高的,就是打表有点难看。

代码如下(稍微改改即可):

#include<algorithm>
#include<cstdio>
using namespace std;
long long max(long long a,long long b){if(a<b)return b;return a;}
int pri[30]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47};
//乘积等于614889782588491410恰好比1e17大一点 
//乘上下一个素数53就是32589158477190044730,有点浪费了 
long long ans_num,ans_tot,n,T,ask[100005],maxn,rec[100005],len;
void dfs(int now,long long num,long long tot,long long top){
	//   当前素数     当前数值     当前因数数     能取到的最大i
	if(num>n||now>=15)return;//15,数组从0开始 
	if(tot>ans_tot){ans_tot=tot;ans_num=num;}
	if(tot==ans_tot&&ans_num>num){ans_num=num;}
	//优先因数个数,其次保证最小
	for(int i=1;i<=top;i++){
		if(num*pri[now]>n)break;
		num*=pri[now];
		dfs(now+1,num,tot*(i+1),i);
	}
}
int main(){
	scanf("%lld",&T);
	for(int i=1;i<=T;i++){
		scanf("%lld",&ask[i]);
		maxn=max(maxn,ask[i]);
	}
	n=maxn;
	while(n!=1){
		ans_num=0;ans_tot=0;
		dfs(0,1,1,57);
		len++;
		rec[len]=ans_num;
		n=ans_num-1;
	}
	sort(rec+1,rec+len+1);
	for(int i=1;i<=T;i++){
		int pos=upper_bound(rec+1,rec+len+1,ask[i])-rec-1;
		printf("%lld\n",rec[pos]);
	}
	return 0;
}

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值