[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)无效。

原文地址:https://www.cnblogs.com/Shallowy/p/9818395.html