[JLOI2014]聪明的燕姿

题面

BZOJ3629
LuoguP4397
LOJ#2234

题意

  给出n(n<=2e9),求约数和为n的数的个数并升序输出。

分析

  看到题:不知道约数和定理,赶紧看一波
  约数和定理
  由唯一分解定理,若\(x=p_1^{a_1}p_2^{a_2} \cdots p_m^{a_m}\) ,
  再由约数个数定理,x的约数个数为\((a_1+1)(a_2+1) \cdots (a_m+1)\) 个,
  再由约数和定理得到x的约数和为\((p_1^0+p_1^1+\cdots p_1^{a_1})(p_2^0+\cdots p_2^{a_2})\cdots(p_m^0+p_m^1+\cdots p_m^{a_m})\).
  
  看到题目发现没什么算法上的思路...

  然后就上爆搜ioi.

  枚举n的质因数\(p_i\)\(p_i\)的指数\(a_i\),dfs时更新当前数和约数和即可.

仔细分析

  口胡完了...

  事实证明,爆搜的时候倒着做总是有奇效!

这里我们一开始把n传进函数里,每次枚举一个质数p和指数a,除以它们对约数和产生的贡献,这样可以很轻松地作答案的判断和合法性的判断。
我一开始传1进去搞得烦死我好菜啊

然后讲一下具体的实现方法和细节

  我们先预处理出\(\sqrt n\),即\(\sqrt{2e9}\)以内的质数以及它们的次幂,并对次幂求前缀和(用于计算约数和),之后进行dfs.
  dfs中按照前面说的,记录3个值:之前枚举过的最大的质数last、当前答案num、当前约数和sum,然后顺序枚举各个未搜过的质数和它的指数,接着进行下一层dfs.
  优化:可以发现,对于当前的sum,满足\(p>\sqrt{sum}\)的质数p中,指数能取到1,即\((p^0+p^1)|sum\)的至多只有1个,且它的值为\(sum-p^0=sum-1\),所以我们只需枚举\(\sqrt {sum}\)以内的质数即可

关于统计答案的两个条件

  1. 若当前的\(sum\)为1,就说明\(num\)是一个可行的答案;
  2. 如果\(sum-1\)是1个质数且该质数没有搜过,则\(num*(sum-1)\)也是一个答案.

好了先上代码

#include <iostream>
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cmath>
#define il inline
#define vd void
#define rg register
#define rep(i,x,y) for(rg int i=x;i<=y;++i)
#define drp(i,x,y) for(rg int i=x;i>=y;--i)
using namespace std;
const int Len=2333333;
char buf[Len],*p1=buf,*p2=buf,duf[Len],*q1=duf;
il char gc(); il int rd(); il vd pc(char c); il vd rt(int x); il vd flush();
int n,cnt,ans,S,vis[23333<<1],p[23333],cn[23333],s[33][23333],q[1233333];
long long po[33][23333];
il int Check(int x){ //判断是否为质数
    if(x<2) return 0;
    if(x==2) return 1;
    int s=sqrt(x);
    for(rg int i=1;i<=cnt&&p[i]<=s;++i) if(!(x%p[i])) return 0;
    return 1;
}
vd Dfs(int last,int num,int sum){
    if(sum==1){q[++ans]=num; return;} //条件1
    if(sum-1>p[last]&&Check(sum-1)) q[++ans]=num*(sum-1); //条件2
    int S=sqrt(sum);
    for(rg int i=last+1;p[i]<=S;++i) //枚举质数
        for(rg int j=1;j<=cn[i];++j) if(!(sum%s[j][i])) //枚举次幂,若sum不能整除s[j][i]则不能往下继续搜
            Dfs(i,num*po[j][i],sum/s[j][i]);
}
int main(){
    rep(i,2,44721){ //44722大概是根号2e9
        if(!vis[i]){
            p[++cnt]=i,s[0][cnt]=po[0][cnt]=1;  //p存质数,po存次幂,s存次幂前缀和
            rep(j,1,32){
                po[j][cnt]=po[j-1][cnt]*i;
                if(po[j-1][cnt]<0){cn[cnt]=j-1; break;} //<0即溢出qwq
                s[j][cnt]=s[j-1][cnt]+po[j][cnt];
                if(s[j][cnt]<0){cn[cnt]=j-1; break;}
            }
        }
        for(rg int j=1;j<=cnt&&i*p[j]<44722;++j){
            vis[i*p[j]]=1;
            if(!(i%p[j])) break;
        }
    }
    while(n=rd()){
        ans=0,Dfs(0,1,n),rt(ans),pc('\n'),sort(q+1,q+ans+1); //得到答案后sort,要求升序输出
        rep(i,1,ans) rt(q[i]),pc(' ');
        if(ans) pc('\n'); //注意ans=0时没有数满足条件,不用换行
    }
    return flush(),0;
}

il char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,Len,stdin),p1==p2)?-1:*p1++;}
il int rd(){char c; int f=1;
    while(!isdigit(c=gc())&&c!='-'&&c!=EOF); if(c==EOF) return 0;
    c=='-'?f=-1,c=gc():0; int x=c^48;
    while(isdigit(c=gc())) x=((x+(x<<2))<<1)+(c^48);
    return x*f;
}
il vd pc(char c){q1==duf+Len&&fwrite(q1=duf,1,Len,stdout),*q1++=c;}
il vd rt(int x){x<0?pc('-'),x=-x:0,pc((x>=10?rt(x/10),x%10:x)+48);}
il vd flush(){fwrite(duf,1,q1-duf,stdout);}

关于条件2

  为什么代码里有\(sum-1>p[last]\)

我们要保证\(sum-1\)这个质数未被搜过。
若它是一个之前已经被搜过的质数,则它一定已经确定了一个指数,我们不能再给它重新置为1,这样会破坏之前已计算出的答案,故此时的\(sum-1\)无效。

posted @ 2018-10-19 19:39 Shallowy 阅读(...) 评论(...) 编辑 收藏