[JZOJ]约数国王(A king)

Description

数学的王国里,有一些约数国王……约数国王的定义是这样的:一个大于 1 1 1的整数 n n n,如果它约数的个数比 1 1 1~ n − 1 n-1 n1的每个整数的约数的个数都要多,那么我们就称它为约数国王。聪明的小明在奥数书上认识了它们,于是产生了一个问题:他想知道L到R之间一共有多少个约数国王?它们分别又是谁?

Input

输入文件只有一行,包含一个 l l l,一个 r r r,表示小明想知道的范围。

Output

只有一行,第一个数 h h h,表示 l l l~ r r r内一共有多少个约数国王,接下来 h h h个从小到大的数(为了防止国王们打架,你需要按顺序输出),表示约数国王分别是谁。

Data Constraint

对于 30 30% 30的数据, 1 ≤ l ≤ r ≤ 200000 1\leq l\leq r\leq 200000 1lr200000
对于 50 50% 50的数据, 1 ≤ l ≤ r ≤ 500000 1\leq l\leq r\leq500000 1lr500000
对于 70 70% 70的数据,保证最大的约数国王的约数的个数不大于 1000 1000 1000
对于 100 100% 100的数据, 1 ≤ l ≤ r 1\leq l\leq r 1lr, 并且保证 l l l r r r 64 64 64位整型以内,最大的约数国王的约数的个数不大于 200000 200000 200000

Solutions

① ① 30 30 30

直接暴力就好了,枚举时注意不要枚举所有因数,只需要枚举两个因数中较小的那个因数,就可以得出答案。

注意完全平方数的特殊情况。
时间复杂度 O ( n n ) O(n\sqrt n) O(nn )

Code

#include<cstdio>
#include<cmath>
#define fo(i,x,y) for(int i=x;i<=y;i++)
#define p 200000
using namespace std;
long long a[200],l,r,t,f,max;
int main()
{
	freopen("king.in","r",stdin);
	freopen("king.out","w",stdout);
    fo(i,2,p)
    {
        f=0;
        fo(j,1,sqrt(i*1.0)) if(i%j==0)f+=2;
        if(floor(sqrt(i*1.0))==sqrt(i*1.0))f--;
        if(f>max){max=f;a[++a[0]]=i;}
    }
    scanf("%lld%lld",&l,&r);
    fo(i,1,a[0])if((a[i]>=l)&&(a[i]<=r))t++;printf("%lld",t);
    fo(i,1,a[0])if((a[i]>=l)&&(a[i]<=r))printf(" %lld",a[i]);
}

② ② 50 50 50

根据乘法原理,设 k = p 1 q 1 ∗ p 2 q 2 ∗ p 3 q 3 … p n q n k=p1^{q1}*p2^{q2}*p3^{q3}…pn^{qn} k=p1q1p2q2p3q3pnqn( p i p_i pi k k k的质因数, q i q_i qi p i p_i pi对应的指数),则 k k k的约数的个数为 ( q 1 + 1 ) ( q 2 + 1 ) ( q 3 + 1 ) … ( q n + 1 ) (q1+1)(q2+1)(q3+1)…(qn+1) (q1+1)(q2+1)(q3+1)(qn+1)
让我们举个栗子:
假设有一数 36 = 2 2 ∗ 3 2 36=2^2*3^2 36=2232
那么其实 2 0 , 1 , 2 ∗ 3 0 , 1 , 2 2^{0,1,2}*3^{0,1,2} 20,1,230,1,2都是 36 36 36的因数,请读者自行思索
因为可以是 0 0 0,所以需要把各指数加一,然后乘起来,就是约数的个数了。
那么,通过枚举 k k k的质因数和指数,也可以求出 k k k的约数的个数。注意同样是枚举小于等于根号 k k k的质因数,不要枚举全部。
P 0 P_0 P0表示根号 n n n以内质数的个数,则时间复杂度 O ( n ∗ P 0 ) O(n*P_0) O(nP0)

Code

#include<cstdio>
#include<cmath>
#define fo(i,x,y) for(int i=x;i<=y;i++)
using namespace std;
const int max=500000,po=sqrt(max*1.0);
bool bz[po+1];
int f[max+1],p[169],a[200],mx,l,r,t;
int main()
{
	freopen("king.in","r",stdin);
	freopen("king.out","w",stdout);
    fo(i,2,po)
    {
               if (!bz[i]) p[++p[0]]=i;
               fo(j,1,p[0])
               {
                           if(max/p[j]<i) break;
                           bz[i*p[j]]=1;
                           if (i%p[j]==0) break;
               }
    }
    fo(i,1,max)
    {
             f[i]=1; int k=i;
             fo(j,1,p[0])
             {
                         if (p[j]>sqrt(i*1.0)) break;
                         if (k==1) break;
                         int a=0;
                         while (k%p[j]==0)
                         {
                               a++;k/=p[j];     
                         }
                         f[i]*=a+1;
             }
             if (k>1) f[i]*=2;
             if(f[i]>mx){mx=f[i];a[++a[0]]=i;}
    }
    scanf("%lld%lld",&l,&r);
    fo(i,1,a[0])if((a[i]>=l)&&(a[i]<=r))t++;printf("%lld",t);
    fo(i,1,a[0])if((a[i]>=l)&&(a[i]<=r))printf(" %lld",a[i]);
}

③ ③ F u l l Full Full S o r e ( 100 ) Sore(100) Sore(100)

首先,我们设一个 c [ i ] c[i] c[i],表示因数个数(包括本数)为 i i i,的最小正整数,那么对于一个数 c [ i ] c[i] c[i],如果 c [ i ] &lt; c[i]&lt; c[i]<任何一个 c [ i + k ] c[i+k] c[i+k] ( k k k为正整数),那么 c [ i ] c[i] c[i]就是一个约数国王。

对于求出 c [ i ] c[i] c[i],我们可以设 P [ i ] P[i] P[i]为从小到大的质数中的第 i i i个。这里用线性筛法可以求出来。

我们可以在设一个F[i,j]表示,有i个不同质因数构成,有j个因数的最小正整数。
那么, c [ i ] = M a x F [ k , i ] c[i]=Max{F[k,i]} c[i]=MaxF[k,i]

求一个数的因子个数的公式:
N = P 1 A 1 + P 2 A 2 + P 3 A 3 + . . . + P m A m N=P1^{A_1}+P2 ^ {A_2}+P3^{A_3}+...+Pm^{A_m} N=P1A1+P2A2+P3A3+...+PmAm也就是 N = N = N= ∑ i = 1 m P i A i \sum\limits^{m}_{i = 1}Pi^{Ai} i=1mPiAi (这里所有的 P P P都是质数),那么, N N N的因数个数为 ( A 1 + 1 ) ( A 2 + 1 ) ( A 3 + 1 ) … ( A m + 1 ) (A1+1)(A2+1)(A3+1)…(Am+1) (A1+1)(A2+1)(A3+1)(Am+1)

证明:

M M M N N N的因数, M = P 1 K 1 + P 2 K 2 + P 3 K 3 + . . . + P m K m M=P1^{K1}+P2^{K2}+P3^{K3}+...+Pm^{Km} M=P1K1+P2K2+P3K3+...+PmKm也就是 M = ∑ i = 1 m P i K i ( K i &lt; = A i ) M=\sum\limits^{m}_{i = 1}Pi^{Ki}(Ki&lt;=Ai) M=i=1mPiKi(Ki<=Ai) ,那么,只要 K i Ki Ki 0 0 0~ A i Ai Ai M ∣ N M|N MN,根据乘法原理, N N N的因数个数 ∏ i = 1 m ( A i + 1 ) ∏\limits^{m}_{i=1}(Ai + 1) i=1m(Ai+1)也就是 ( A 1 + 1 ) ( A 2 + 1 ) ( A 3 + 1 ) . . . ( A m + 1 ) (A1+1)(A2+1)(A3+1)...(Am+1) (A1+1)(A2+1)(A3+1)...(Am+1)

那么F的转移方程如此:
F [ i + 1 , j ∗ ( k + 1 ) ] = m i n ( F [ i , j ] ∗ P [ i + 1 ] k ) F[i+1,j∗(k+1)]=min(F[i,j]∗P[i+1]^k) F[i+1,j(k+1)]=min(F[i,j]P[i+1]k)

为什么要乘以 P [ i + 1 ] k P[i+1]^k P[i+1]k呢,你想想,选i+1个素数,当然是选前i个最优。

那我们来讨论一下j的取值范围,可想到 2 i &lt; = j &lt; = m x 2^i&lt;=j&lt;=mx 2i<=j<=mx, m x mx mx为数组开的下限,为什么是 2 2 2 i i i次方呢,我们在选第 i + 1 i+1 i+1个素数时,前面已经选了 i i i个了,每一个素数最少选 1 1 1个,就会有 2 i 2^i 2i个因子,故如此。

那么代码就很容(kun)易(nan)想象出来了

Code:

空间复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn),不算很大。但是我们打个滚动数组会更好,时间也会小一些。

# include<cstdio>
# include<cmath>
# define fo(i,x,y) for(int i=x;i<=y;i++)
const int am=170000,an=17,up=100;
int min[am+1],min1[am+1];
bool bz[up+1];
long long a[300],p[up+1],f[am+1],f1[am+1],m[am+1];
long long max=9223372036854775807LL;
int main()
{
	freopen("king.in","r",stdin);
	freopen("king.out","w",stdout);
    fo(i,2,up)
    {
        if (!bz[i]) p[++p[0]]=i;
        fo(j,1,p[0])
        {
            if (up/p[j]<i) 
				break;
            bz[i*p[j]]=1;
            if (i%p[j]==0) 
				break;
        }
    }
    f[1]=1;
    fo(i,2,62)
    {
        f[i]=f[i-1]*2;
        min[i]=i-1;
    }
    fo(i,63,am)f[i]=max;
    fo(i,2,am) m[i]=max;
    fo(i,1,16)
    if (i%2)
    {
        fo(j,1<<(i+1),am) f1[j]=max;
        fo(j,1<<i,am)
        {
            if (f[j]==max) continue;
            if (f[j]<m[j]) m[j]=f[j];
            double y=log(max/f[j])/log(p[i+1]);
            if(am/(j+1)-1<y) y=am/(j+1)-1;
            if(min[j]<y) y=min[j];
            long long op=1;
            fo(k,1,y)
            {
                op*=p[i+1];
                if(f[j]*op<f1[j*k+j])
                {
                    f1[j*k+j]=f[j]*op;
                    min1[j*k+j]=k;
                }
            }
        }
    }
    else
    {
        fo(j,1<<(i+1),am) f[j]=max;
        fo(j,1<<i,am)
        {
            if (f1[j]==max) continue;
            if (f1[j]<m[j]) m[j]=f1[j];
            double y=log(max/f1[j])/log(p[i+1]);
            if(am/(j+1)-1<y) y=am/(j+1)-1;
            if(min1[j]<y) y=min1[j];
            long long op=1;
            fo(k,1,y)
            {
                op*=p[i+1];
                if(f1[j]*op<f[j*k+j])
                {
                    f[j*k+j]=f1[j]*op;
                    min[j*k+j]=k;
                }
            }
        }
    }
    fo(i,1<<17,am)
		if(f[i]<m[i])
			m[i]=f[i];
    long long x=max;
    for(int i=am; i>1; i--)
        if(m[i]<x)
        {
            a[++a[0]]=m[i];
            x=m[i];
        }
    long long l,r;
    scanf("%lld %lld",&l,&r);
    int y=0;
    fo(i,1,a[0]) 
		if ((a[i]>=l)&&(a[i]<=r)) 
			y++;
    printf("%d",y);
    for(int i=a[0]; i>=1; i--)
        if ((a[i]>=l)&&(a[i]<=r))
            printf(" %lld",a[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值