acWing 1296 聪明的燕姿

用到了算数基本定理的思想,欧拉筛的不记录最小质因子的板子,另外在优化dfs的时候还用到了小学生求素数的一点小思想,下面我们简单说一下思路和分析几个问题。
思路:正约数之和等于s的数,容易想到直接枚举每一个质数与每一个质数的数量,然后进行优化。
第一个问题:欧拉筛存质数的数组到底开多大?
这个问题,谁一开始写这个代码也不知道,除非花了很多时间分析,所以我们先保留这个问题。
第二个问题:如何优化枚举。
(1)先枚举数量还是先枚举质数?
答:先枚举质数比较好,因为我们可以让下一次dfs调用,枚举素数开始的位置为上一次枚举素数结果的下一项,也是一种小优化。
(2)枚举素数的终止条件是什么?
**答:**我们每一层dfs都有一个now表示的是s除以每一层dfs得到的乘数之后的结果,只有(1+p+p^2+…+ p ^ k) 为因子时,我们才算找到一个结果,那么对于很大的p来说,只要p>sqrt(now),那么其实到1+p+p ^ 2 来说,乘数就已经比now还要大了,那么其实我们对每一层的dfs就可以就行如下优化了
1.判断1+p (这里的p我们默认为大于sqrt(now))是否等于now,然后判断p是否为素数,这样就排除了所有大于sqrt(now)的p的正解。
2.枚举素数时,我们只枚举sqrt(now)以内的素数,枚举数量时,枚举到(1+p+p^2+…+ p ^ k) 小于等于now即可
最后悄悄说一句:wlz是傻dei

1296. 聪明的燕姿
城市中人们总是拿着号码牌,不停寻找,不断匹配,可是谁也不知道自己等的那个人是谁。

可是燕姿不一样,燕姿知道自己等的人是谁,因为燕姿数学学得好!

燕姿发现了一个神奇的算法:假设自己的号码牌上写着数字 S,那么自己等的人手上的号码牌数字的所有正约数之和必定等于 S。

所以燕姿总是拿着号码牌在地铁和人海找数字(喂!这样真的靠谱吗)。

可是她忙着唱《绿光》,想拜托你写一个程序能够快速地找到所有自己等的人。

输入格式
输入包含 k 组数据。

对于每组数据,输入包含一个号码牌 S。

输出格式
对于每组数据,输出有两行。

第一行包含一个整数 m,表示有 m 个等的人。

第二行包含相应的 m 个数,表示所有等的人的号码牌。

注意:你输出的号码牌必须按照升序排列。

数据范围
1≤k≤100,
1≤S≤2×109
输入样例:
42
输出样例:
3
20 26 41

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1 * 1e6 + 15;
typedef long long ll;
int s,cmy,k;
int res[N];
ll p[N];
bool book[N];
void oula()
{
	for(int i = 2; i < N; i ++)
	{
		if(!book[i])
		{
		    p[++cmy] = i;
		}
		for(int j = 1; j <= cmy && p[j] * i < N; j ++)
		{
			int t = p[j] * i;
			book[t] = true;
			if(i % p[j] == 0) break;
		}
	}
}
bool is_prime(ll x)
{
	if(x < N) return !book[x];
	else
	{
		if(x == 1) return false;
		if(x == 2) return true;
		if(x % 2 == 0) return false;
		for(int i = 3; i <= sqrt(x); i += 2) if(x % i == 0) return false;
		return true;
	}
}
void dfs(int last, ll now, ll sniper)
{
	if(now == 1)
	{
		res[k ++] = sniper;
		return;
	}
	
	if(now - 1 > p[last] && is_prime(now - 1))
	{
		res[k ++] = sniper * (now - 1);
	}
	
	for(int i = last + 1; i <= cmy && p[i] * p[i] <= now; i ++)
	{
		for(ll j = p[i] + 1, tmp = p[i]; j <= now; tmp *= p[i], j += tmp)
		{
			if(now % j == 0) dfs(i , now / j, sniper * tmp);
		}
	}
}
int main()
{
	oula();
	while(scanf("%d",&s) != EOF)
	{
		k = 0;
		dfs(0,s,1);
		sort(res,res+k);
		bool flag = false;
		printf("%d\n",k);
		for(int i = 0; i < k; i ++)
		{
			if(!flag)
			{
				flag = true;
				printf("%d",res[i]);
			}
			else printf(" %d",res[i]);
		}
		if(k) puts("");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值