数论1 素数+约数+反素数

数论1 素数+约数+反素数

素数:

素数是指只能被自身整除和被1整除的数(大于1的自然数,1不是素数)不是素数的数为合数
常见的题型有:素数的判定+素数的筛选两种题型

  • 素数的判定:
    • 试除法(如果n不能被【0,根号n】内的所有数整除,那么就为素数)
    • 加速技巧:>4的素数总是会被 6x+1 或 6x-1整除
  • 素数的筛选:
    • 素数埃氏塞法(复杂度:n* log log n,素数分布:x/ln x)代码略(不断塞出素数的倍数)
    • 线性塞法(欧拉塞法)(复杂度 n) 常用
const int N=1000001;
int primes[N],cnt;//primes存素数 cnt 存素数的个数
bool st[N];//存数n是否被筛过
void getprimes(int n)
{
    memset(st,false,sizeof st);//每个数都没有被筛过,初始化
    cnt=0;
    for(int i=2;i<=n;i++)
    {
        if(!st[i]) 
            primes[cnt++]=i;//没有被筛过就是素数
        for(int j=0;j<cnt &&   i*primes[j]<=n;j++)//
        {
            st[i*primes[j]]=true;
            if(i%primes[j]==0)
                break;
        }
    }
}

约数:

若整数n除以整数d的余数是0 ,即d能整除n,则称d是n的约数, n是d的倍数
对于一个数我们可以质因数分解,所以对于每个数n
我们可以分解成 p1 ^ c1+p2 ^ c2+……+px ^ cx
而约数个数也就是(c1+1) * (c2+1) * …… (cx+1)
而约数和就是 (1+p1+p1^ 2 +p1^ 3+p1^ 4……+p1^ c1) (1+1+p2+p2^ 2+p2^ 3+p2^ 4…… +p2^ c2)……(1+pn+pn^ 2+pn^ 3……pn^ cn)

  • 约数个数
    对于线性筛选,我们其实就是在筛选质数的时候顺便求出约数个数,我们知道线性筛选就是筛出质数然后再枚举最小质因子去进行筛选。我们先定义两个数组t,和e,t[i]表示i这个数约数个数,e[i]表示i这个质因数分解后最小质因子的次方,即c1。
    我们对x进行分类讨论,如果x是质数的话,那么t[x]=2,e[x]=1。这个就是质数的因子只有自己和1嘛,e[x]=1是因为1不是质数,所以i的最小质因子是自己,这个当然就是1了。
    然后我们就到了筛质数的环节了,在这里我们会处理x不是质数的情况,对于x我们可以在筛时这么写i*prime[j](这个是筛质数的操作,prime数组是存质数的),所以我们还要对i和prime[j]进行分类讨论。
    对于i是prime[j]的倍数,那么t[x]=t[i]/(e[i]+1) * (e[i]+2),为什么是这个?我们先看e数组把,因为和e数组有关,因为i是prime[j](我们是枚举最小质因子去筛的)的倍数所以x的最小质因子指数会多1,所以e[x]=e[i]+1,所以我们根据之前给的约数个数公式可以知道其实就是除以(e[i]+1),乘以(e[i]+2),这就是上面公式的由来。
    对于i不是prime[j]的倍数,那么e[x]=1,因为prime[j]是我们枚举的最小质因子,所以这个只有一个。因为约数个数这个函数是积性函数,所以t[x]=t[i] * t[prime[j]],因为t[prime[j]]的值一定是2,因为prime[j]是质数,所以我们可以简化写成t[x]=t[i] * 2。
    这样我们就讨论完了。
#include<iostream>
#include<cstdio>
using namespace std;
long long prime[3000100],t[3000100],e[3000100];
int vist[3000100];
int main()
{
	int n,num=0;
	scanf("%d",&n);
	t[1]=1;//初值,1的约数个数是1 
	for(int i=2;i<=n;i++)
	{
		if(vist[i]==0)//质数的话 
		{
			prime[++num]=i;
			t[i]=2;
			e[i]=1;
		}
		for(int j=1;j<=num;j++)//用最小质因子去筛 
		{
			if(i*prime[j]>n)//超过了n就不用了 
				break;
			vist[i*prime[j]]=1;//打下标记是合数 
			if(i%prime[j]==0)//分类讨论 
			{
				t[i*prime[j]]=t[i]/(e[i]+1)*(e[i]+2);//根据我们的公式计算 
				e[i*prime[j]]=e[i]+1;
				break;
			}
			else //不是倍数的时候 
			{
				t[i*prime[j]]=t[i]*2;
				e[i*prime[j]]=1;
			}
		}
	}
	for(int i=1;i<=n;i++)
		printf("%lld\n",t[i]);//输出答案 
	return 0;
}

约数和

既然有约数个数,那么跟定有约数和,现在我们就来讲一下怎么计算约数和。
根据上面约数个数的思想,我们也用分类讨论来计算约数和。
同样我们要定义两个数组,一个是t,t[i]表示i的约数和,同样我们也要一个辅助的数组e,e[i]表示不能被最小质因子整除的约数的和。
首先如果x是质数的话,约数只有自己和1,那么t[x]=x+1,而不能被最小质因子整除的数也只有1,所以e[x]=1。
然后我们再用i*prime[j]去表示非质数的x,同样,也分i可以整除prime[j]和不能整除。
不可以整除的话,意味着t[i]和t[prime[j]]这两个是没有重复部分的,因为他们这两个数是互质的,证明一下吧,因为prime[j]是个质数,所以如果不是互质的话只有一种可能,就是i是prime[j]的倍数(这就是我们要这么分类讨论的原因,上面的分类讨论也是因为这个)。所以t[x]=t[i]*prime[j]+t[i],化简一下就是t[x]=t[i] * (prime[j]+1)。而e因为不是倍数所以e[x]=t[i]
如果i是prime[j]的倍数的话因为他们中有重复的,重复的部分就是可以被最小质因子整除的约数和,也就是t[i]-e[i],那么我们为什么不用e数组直接存这个呢?因为不方便计算啊。所以我们就是t[x]=t[i] * prime[j]+t[i]-(t[i]-e[i]),化简一下就是t[x]=t[i]*prime[j]+e[i],而e因为是倍数所以我们要继承一下e[x]=e[i],这样就计算完了。

#include<iostream>
#include<cstdio>
using namespace std;
long long vist[3000100],prime[3000100],t[3000100],e[3000100];
int main()
{
	int n,num=0;
	scanf("%d",&n);
	t[1]=1;//边界 
	for(int i=2;i<=n;i++)
	{
		if(vist[i]==0)//质数的情况 
		{
			prime[++num]=i;
			t[i]=i+1;
			e[i]=1;
		}
		for(int j=1;j<=num;j++)
		{
			if(i*prime[j]>n)
				break;
			vist[i*prime[j]]=1;//打个标记 
			if(i%prime[j]==0)//如果是倍数 
			{
				t[i*prime[j]]=t[i]*prime[j]+e[i];
				e[i*prime[j]]=e[i];
				break;
			}
			else//不是倍数 
			{
				t[i*prime[j]]=t[i]*(prime[j]+1);
				e[i*prime[j]]=t[i];
			}
		}
	}
	for(int i=1;i<=n;i++)
		printf("%lld\n",t[i]);//输出 
	return 0;
}

反素数

#include <iostream>
#include <stdio.h>

using namespace std;

typedef unsigned long long ull;
#define INF ((ull)1)<<62
const int maxn = 50000;
ull ans;
int Type,K;
int d[maxn];   

int prime[] = {2,3,5,7,11,13,17,19,23,29,31,37,41,47,53};

void create_table()
{
    
    for(int i = 1; i < maxn; i++)
        d[i] = i;
    for(int i = 1; i < maxn; i++)
    {
        for(int j = i; j < maxn; j += i)
            d[j]--;   
        
        if(!d[d[i]])
            d[d[i]] = i;
       
        d[i] = 0;
    }
}

void dfs(int pos,ull value,int num)
{
    if(num > K || pos > 15)
        return;
    if(num == K)
    {
        if(value <ans)
            ans=value;
        return;
    }
    for(int i = 1; i <= 63; i++)
    {
        if(value > ans/prime[pos] || num*(i+1)>K)
            break;
        value *= prime[pos];
        if(K%(num*(i+1))==0)
            dfs(pos+1,value,num*(i+1));
    }
}
int main()
{
    int T,t=1;
    create_table();  
    cin>>T;
    while(T--)
    {
        cin>>Type>>K;
        cout<<"Case "<<t++<<": ";
        if(Type)
        {
            if(d[K])
                cout<<d[K]<<endl;
            else
                cout<<"Illegal"<<endl;
        }
        else
        {
            ans = 1e19;
            dfs(0,1,1);
            if(ans > INF)
                cout<<"INF"<<endl;
            else
                cout<<ans<<endl;
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值