BZOJ 3629&&洛谷4397[JLOI2014] 聪明的燕姿

看那么多人给燕姿打call,那就默默+1

题里说的很清楚,约数和等于s的数是答案

先普及一些东西

一个数可以分解为P1^a1*P2^a2*.......*Pn^an,其中P1~Pn都是素数,(算术基本定理:任何一个大于 1 的自然数(不为素数)可以分解成一些素数的乘积;并且在不计次序的情况下,这种分解方式是唯一的。),这个定理,我们可以说是显然的,我们从2开始,如果他MOD2==0,说明可以被2整除,那就把它除以2^n,直到不能被2整除,那这时,2的所有倍数都不能被当前数整除,(抹掉了一大片合数),然后是3,同2,3的倍数也都是合数,此时他们也不能被当前数整除,然后4,4是2的倍数,不能被整除,然后是5,以此类推,之后便可以发现当前数被分成了若干个质数的幂次方的乘积,存在性可以运用反证法证明,若当前数被分解完后,存在一个合数的幂次方,假设这个合数的两个因子为q和p,那么如果他们都是合数,那么又可以这么分下去,直到分成若干个素数相乘,那么当前数又变成了素数的乘积。

然后是  约数个数定理

由上面的数的分解可以看出一个显然的事情:P1^0,P1^1,P1^2.......P1^a1都是当前数的约数,共(a1+1)个,那么同理,由乘法原理可得,当前数的约数共(a1+1)*(a2+1)*.......*(an+1)个。

约数和定理

令n的正约数和为f(n)

 

可知p1^a1的约数有:p1^0, p1^1, p1^2......p1^a1

同理可知,pk^ak的约数有:pk^0, pk^1, pk^2......pk^ak ;

n的约数是在p1^a1、p2^a2、...、pk^ak可以看为每一个的约数中分别挑一个相乘得来,

可知共有(a₁+1)(a₂+1)(a₃+1)…(ak+1)种挑法,即约数的个数。

由乘法原理可知它们的和为

f(n)=(p1^0+p1^1+p1^2+…p1^a1)(p2^0+p2^1+p2^2+…p2^a2)…(pk^0+pk^1+pk^2+…pk^ak)

之后我们可以发现,从每个小括号里取出一项,一个小惊喜,乘起来后,又变成了一个数的分解形式,并且,你会发现这个递归数的约数和(f(n)),就是你的单个括号内所有数之和 。

另外,还有一点,如果当前数-1是一个素数,那么当前数-1也是一组可行解,为什么呢,当前数-1是素数,那么他的约数只有他和1,那么约数和就是他+1,就是原数

欧拉线性筛

一开始时要预处理出所有素数,但是2*10^9,有点大,所以要将素数存入数组中

欧拉线性筛保证每个合数被他的最小质因子删掉了,并且只删了一次,所以,复杂度为o(n);

好了,下面给出上面涉及到的涉及到约束的定理以及AC代码(分解为P1^a1形式就把第一个代码变成k++,然后输出^k)

 update 18-09-03

//By AcerMo
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=105000;
int n;
int num[M],pri[M],vis[M],tot=0,cnt=0;
inline void getpri()
{
	pri[0]=pri[1]=1;
	for (int i=2;i<M;i++)
	{
		if (!vis[i]) pri[++tot]=i;
		for (int k=1;k<=tot&&pri[k]*i<M;k++)
		{
			vis[pri[k]*i]=1;
			if (i%pri[k]==0) break; 
		}
	}
	return ;
}
inline bool che(int x)
{
	if (x<2) return 0;
	for (int i=1;pri[i]<=sqrt(x);i++) 
	if (x%pri[i]==0) return 0;
	return 1;
}
inline void dfs(int x,int pr,int la)
{
	if (x==1) return (void)(num[++cnt]=pr);
	if (x-1>=pri[la]&&che(x-1)) num[++cnt]=(x-1)*pr;
	for (int i=la;pri[i]<=sqrt(x);i++)
	{
		int npr=pri[i];
		for (int k=pri[i]+1;k<=x;k+=npr)
		{
			if (x%k==0) dfs(x/k,npr*pr,i+1);
			npr*=pri[i];
		}
	}
	return ;	
} 
signed main()
{
	getpri();
	while (~scanf("%d",&n)&&n)
	{
		fill(num,num+cnt+1,0);
		cnt=0;dfs(n,1,1);printf("%d\n",cnt);
		if (!cnt) continue;sort(num+1,num+cnt+1);
		for (int i=1;i<=cnt;i++) printf("%d ",num[i]);
		puts("");
	}
	return 0;
}

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值