BZOJ3629 聪明的燕姿 【例题精讲】

    题意:给出一个数S,求约数和等于S的数。
     这是一道数学题,深搜只是个幌子,如果是纯代码的话,会非常难读,所以我写一份比较详细的题解,但恐怕还是不能把这道题的美妙写尽,罢了,数学的精彩是需要有心人领会的,可惜我没心;
     在解决这道题之前,在数学上需要储备两项知识:
1.算数基本定理:任何一个大于1的自然数N,都可以唯一分解成有限个质数的乘积N=P^aP^a…Pn^an,这里P₁<P₂<…<Pn均为质数,其诸指数ai是正整数。
2.约数和定理:对于任意一个大于1的正整数N可以分解正整数:N=P₁^a₁ P₂^a₂…Pn^an,则由约数个数定理可知N的正约数有(a+1)(a₂+1)(a₃+1)…(an+1)个,那么N(a₁+1)(a₂+1)(a₃+1)…(an+1)个正约数的和为f(N)=(P^0+P^1+P^2+…P^a)(P^0+P^1+P^2+…P^a)…(Pn^0+Pn^1+Pn^2+…Pn^an)
    建议上网搜或者翻课本来加深对这两个知识的理解;
    题中涉及了筛素数,所以建议大家掌握筛法的优化,这里不多解释,网上写的很多,也足够清楚;
    接下来咱们马上看整个程序最核心的思想,我们学了约数和定理,是用来求原数的约数和的,但是这道题是给了一个约数和,让我们找所有可能的原数,所以不免会用到枚举,我们可以从这个式子入手:
        f(N)=(P^0+P^1+P^2+…P^a)(P^0+P^1+P^2+…P^a)…(Pn^0+Pn^1+Pn^2+…Pn^an)
    我们可以看到,p1p2这样的素数我们可以递增去枚举,对于指数a可以用循环判断来做到,当我们确定了一组可行的因式时(形如(P^0+P^1+P^2+…P^a)),就可以把它从原式里除去,这就达到了拆分原数的目的;根据题意我们可以知道,我们把原式拆成1之后,就需要看我怎么得到我要的结果,我们仔细观察这个式子
拆完之后,P₁^a₁*P₂^a₂*...*Pn^an 这就是我想得到的一组解,这个数的约数和就是原数!为什么呢?因为在原式中,P₁^0、P₁^1...这些都是P₁^a₁的因式,以此类推,所以分解因式之后再加起来,就是我要的原数,这一点很神奇;
    另外,我如果在搜索的时候,拆剩下的数减1是一个新的质数,那么就可以看成是Pn^0+Pn^1这种形式,即是满足上面列的等式的,我只需要把以前拆出来的P₁^a₁*P₂^a₂*...等各项再乘给Pn^1,依然会得到一个非常合法的结果,这样,这道题就算得以解决了,只是有些代码问题还非常谜,我会给出比较详细的注释的;
        现在分享代码:
 
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<string>
 4 #include<cstring>
 5 #include<cstdlib>
 6 #include<cmath>
 7 #include<algorithm>
 8 #include<queue>
 9 #include<stack>
10 #include<iterator>
11 using namespace std;
12 const int MAXN=100010;
13 bool nop[MAXN];int isp[MAXN];
14 long long ans[MAXN],sum,num,cnt=0;
15 long long n,m;
16 void prime()//筛素数; 
17 {
18     nop[0]=nop[1]=1;
19     for(int i=2;i<=MAXN;i++){
20         if(!nop[i]) isp[++cnt]=i;
21         for(int j=1;j<=cnt&&isp[j]*i<MAXN;j++){
22             nop[isp[j]*i]=true;
23             if(i%isp[j]==0) break;
24         }
25     }
26 }
27 bool pd(long long x)//判素数 
28 {
29     if(x==1)return 0;
30     for(int i=1;isp[i]*isp[i]<=x;i++){
31         if(x%isp[i]==0)return 0;
32     }
33     return 1;
34 }
35 void dfs(long long now, int k, long long left)//now是目前搜出的结果(形如pow(P1,a1)*pow(P2,a2)*...这样的式子),k是目前用到的素数,left是把原数拆分后剩余的部分; 
36 {
37     if(left==1){//如果原式被拆完了,那就得出结果; 
38         ans[++cnt]=now;
39         return ;
40     }
41     if(left-1>=isp[k]&&pd(left-1)){
42         ans[++cnt]=(left-1)*now;//题解中补充的问题,望诸位理智吸收 
43     }
44     for(int i=k;isp[i]*isp[i]<=left;i++){
45         for(long long tmp=isp[i]+1,tt=isp[i];tmp<=left;tt*=isp[i],tmp+=tt){
46             if(left%tmp==0){//这是在拆分P和a,tt是用来完成乘方运算的,tmp是来完成各约数相加的;
47                 dfs(tt*now,i+1,left/tmp); //"/tmp"是在拆式子,tmp是各种因式; 
48             }
49         }
50     }
51     return ;
52 }
53 int main()
54 {
55     prime();
56     while(~scanf("%lld",&n)){
57         memset(ans,0,sizeof(ans));
58         cnt=0;
59         dfs(1,1,n);
60         sort(ans+1,ans+1+cnt);
61         printf("%lld\n",cnt);
62         for(int i=1;i<cnt;i++){
63             printf("%lld ",ans[i]);
64         }
65         if(cnt)
66         printf("%lld\n",ans[cnt]);
67     }
68     return 0;
69 }
View Code

 

 
posted @ 2018-04-05 10:18  杜宇一声  阅读(280)  评论(0编辑  收藏  举报