• [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('
    '),sort(q+1,q+ans+1); //得到答案后sort,要求升序输出
    		rep(i,1,ans) rt(q[i]),pc(' ');
    		if(ans) pc('
    '); //注意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)无效。

  • 相关阅读:
    一题多解 —— 同时找到序列的最大值最小值
    中位数与顺序统计量
    软件开发 —— 极限编程(XP:Extreme Programming)
    一题多解 —— 二项式分布的期望和方差的计算
    C++中explicit关键字用法
    【联系】二项分布的对数似然函数与交叉熵(cross entropy)损失函数
    随机变量统计独立性的相关证明
    PowerShell管理SCOM_批量设置维护模式(下)
    导出AD用户所属组,查询AD用户(aduser)
    SQL脚本运行
  • 原文地址:https://www.cnblogs.com/Shallowy/p/9818395.html
Copyright © 2020-2023  润新知