• 从线性筛到欧拉函数,你十有八九能懂吧!


    这篇文章的动机:万一有什么长进呢?(先痴想一下吧)
    本文难度:普及-→提高+

    (Part;1).线性筛

    不止能筛质数,所有的积性函数都可以(O(n))筛出。
    (ps):积性函数只对于(forall x,yin P)(这里(P)是质数的集合),(f(ab)=f(a)f(b))的数论函数。
    当然不排除你喜爱埃氏筛,但洛谷(1e8)照样会卡你。
    但不能略过:
    埃氏筛,全称是啥显然忘了(用不到吧(qwq)?),核心思想是利用已有的质数,与当前的数相乘,得到的一定是合数。
    下面是线性筛质数:

        judge[1]=true;
        for(int i=2;i<=maxn;i++)
        {
            if(i*i>maxn) continue;
            if(!judge[i])
            {
                for(int j=2*i;j<=maxn;j+=i) judge[j]=true;//是合数
            }
        }
    

    一看就懂吧。
    复杂度分析(并不会):
    我们知道素数密度是(dfrac{n}{ln n})的,那么对于每个合数(x)要筛(dfrac{maxn}{x})次然而他求和是接近(log n)的(难死了),所以应该是(O(nloglog n)),近似于常数了(这改变不了他被卡的命运)
    那大神欧拉发现(这谁都可以),有些数会被筛好几次,这就是他不能(O(n))的原因,比如(6),被(2,3)筛两次,那我们可以这样写:

        for(int i=2;i<=maxn;i++)
        {
        	if(!judge[i]) prime[++cnt]=i;
        	for(int j=1;j<=cnt&&(prime[j]*i)<=maxn;j++)
        	{
        		judge[i*prime[j]]=true;
        		if(i%prime[j]==0) break;
    	}
        }
    

    为什么只会筛一次呢?
    考虑如果整除,那就有一个平方因子也可以筛掉他,丢到后面去就好了。
    所以复杂度就是(O(n))的,再卡就没办法了。

    (Part;2).欧拉函数

    定义欧拉函数(psi(n))为与(n)互质的数,一个大佬讲得贼好:
    (psi(6))咋算?这里是可以枚举的,是(O(nlog n)),他讲了种好方法:
    写出所有分母是(6)的不大于(1)的分数。

    [dfrac{1}{6},dfrac{2}{6},dfrac{3}{6},dfrac{4}{6},dfrac{5}{6},dfrac{6}{6} ]

    化简后分组:

    [dfrac{1}{6},dfrac{1}{3},dfrac{1}{2},dfrac{2}{3},dfrac{5}{6},dfrac{1}{1} ]

    那么:

    [psi(6)=2,psi(3)=2,psi(2)=1,psi(1)=1 ]

    然后就说明:

    [sum_{d|n} psi(d)=n ]

    显然观察归纳不严密,若我有幸学习并会了了莫反再来证吧。

    (Part;3).线性求法

    欧拉函数是积性函数,保证了它可以被(O(n))筛出,但原理较复杂,建议先别学(qwq),看完下面就好多了,反正我是

    (Part;4).单个数的欧拉函数

    考虑一个事:

    [ ext{若}xin P,psi(x)=x-1 ]

    只有自身不互质,我们称为性质一。

    性质二:考虑一个合数(a)满足(a=p^k(k>1,kin Z)),显然与他互质的数(bin [p,2p,3p......p^{k-1}p])(饶了我吧,我真不会打大括号)。
    那么(psi(a)=p^k-p^{k-1}=p^k(1-dfrac{1}{p}))

    性质三:最一般的情况,每个合数(a)都可写成唯一分解式:

    [a=p_1^{k_1}p_2^{k_2}......p_n^{k_n} ]

    即:

    [a=prod_{i=1}^n p_i^{k_i} ]

    好在欧拉函数是个特殊的积性函数,满足(psi(nm)=psi(n)psi(m)),成立(当然在定义域)。
    那么

    [psi(a)=prod_{i=1}^n psi(p_i^{k_i})=prod_{i=1}^n p_i^{k_i}prod_{i=1}^n(1-dfrac{1}{p_i})=aprod_{i=1}^n(1-dfrac{1}{p_i}) ]

    到这里,我们就可以(O(sqrt{n}))得出 一个数的欧拉函数了。 其实思想和早先我们学习质数验证枚举因子是异曲同工的:
    代码:

    int phi(int n)
    {
    	int ans=n;
    	for(int i=2;i<=sqrt(n);i++)
    	{
    		if(n%i==0)
    		{
    			ans=ans/i*(i-1);//这是一个p,注意先除再乘,防止炸int
    			while(n%i==0) n/=i; //把质数次方因子筛没了,就不会错了
    		}
    	}
    	if(n>=2) ans=ans/n*(n-1);//最后有可能剩下
    	return ans;
    }
    

    不过有些人不这样写:

    int phi(int n)
    {
    	int ans=1,now;
    	for(int i=1;i<=sqrt(n);i++) 
    	{
    		now=1;
    		if(n%i==0)
    		{
              	now=i-1,n/=i;
    			while(n%i==0) now*=i,n/=i;
    		}
    		ans*=now;
    	}
    	if(n!=1) ans*=n-1;
    	return ans;
    

    简单说下,对于一小部分((p^k))考虑变形:

    [psi(p^k)=p^k-p^{k-1}=p^{k-1}(p-1) ]

    于是发现出现质数(p)先乘一次(p-1),再来(k-1)(p),其他都是一样的,不过这里:

    now=i-1,n/=i;
    

    注意除一下,然后(now,ans)分别记录,不能够混淆。

    (Part;5).回归(Part;3)

    剩下的就好说了。

    phi[1]=1;
    	for(int i=2;i<=n;i++)
    	{
    		if(!vis[i]) p[++cnt]=i,phi[i]=i-1;//质数只有自身与自己不互质 
    		for(int j=1;p[j]&&i*p[j]<=n;j++)
    		{
    			vis[i*p[j]]=1;
    			if(!(i%p[j]))
    			{
    				phi[i*p[j]]=phi[i]*p[j];
    				//这里是平方因子了,不要减1 
    				break;
    			}
    			else phi[i*p[j]]=phi[i]*(p[j]-1);//第一次出现因子,乘p-1 
    		}
    	}
    

    精华都在代码里了。
    其实和筛素数的是一样的。
    线性筛可是很(NB)的,以至于所有积性函数都怕他(掩盖不了看似大的惊人的复杂度了)。

    (Part;6) 欧拉定理

    前置知识:快速幂。
    大概这样写,复杂度是(O(log n))的。

    ll quickpow(ll a,ll b)
    {
        ll ans=1,base=a;
        while(b!=0)
    	{
            if(b&1!=0)//b%2==1;
    		{
                ans*=base;
            }
            base*=base;
            b>>=1;//b/=2;
        }
        return ans;
    }
    

    你甚至可以自己定义乘法运算跑快数幂。
    可是指数很大怎么办呢?
    (O(log n))都过不去啊!
    伟大的欧拉提出了定理,我们为了纪念他,称为欧拉定理

    (gcd (a,m)=1),则:

    [a^{psi(m)}equiv1pmod{m} ]

    但他认为不够,又提出了扩展欧拉定理

    [a^bequivegin{cases}a^b&b<psi(m)\a^{b;mod;psi(m) +psi(m)}&bgeqslant psi(m)end{cases} ]

    这样,先预处理出(psi(m)),再用字符串边读边模即可。
    复杂度大概(O(len_b+sqrt{m}+log m))(毕竟(psi(m))上界是(m)),可以通过本题。
    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    int phi(int x)//欧拉函数
    {
    	int ans=1,num=1;
    	for(int i=2;i*i<=x;i++)
    	{
    		if(!(x%i))
    		{
    			num=i-1,x/=i;
    			while(!(x%i)) num=num*i,x/=i;
    			ans=num*ans;
    		}
    	}
    	if(x!=1) ans=ans*(x-1);
    	return ans;
    }
    inline int read(int mod)//改进快读,让他边读边输入
    {
    	//g用来判断b与phi(m)的大小,如果小于,就不能加了,这是坑点!
    	int x=0;
    	bool g=false;
    	char c=getchar();
    	while(c<'0'||c>'9') c=getchar();
    	while(c>='0'&&c<='9')
    	{
    		x=(x<<3)+(x<<1)+(c^'0');
    		if(x>=mod) x%=mod,g=true;
    		c=getchar();
    	}
    	if(g) return (x+mod);
    	else return x;
    }
    int a,mod;
    char b[20000005];
    inline int quickpow(int a,int b)//快速幂
    {
    	long long ans=1,base=(long long)a;
    	while(b)
    	{
    		if(b&1) ans=ans*base%mod;
    		b>>=1;
    		base=base*base%mod;
    	}
    	return (int)(ans%mod);
    }
    int p;
    int main()
    {
    	scanf("%d%d",&a,&mod);
    	int p=phi(mod);
    	int cishu=read(p);//得出的化简次数
    	int s=quickpow(a,cishu);
    	printf("%d
    ",s);
    	return 0;
    }
    

    大概就这些啦,还有些高深点的东西以后再更。

  • 相关阅读:
    git各种操作:基本操作 and 多人协作 and 冲突解决
    mini-ndn0.5.0 安装教程 (避免踩坑)
    ubuntu18.04 更换镜像源
    自定义日志轮转
    linux常用命令
    Thinkphp6 + uniapp框架,接口访问跨域问题
    机器学习-主成分分析PCA与奇异值分解SVD
    机器学习-数据预处理&特征工程
    机器学习-梯度提升树(xgboost)
    github打不开怎么办
  • 原文地址:https://www.cnblogs.com/tlx-blog/p/12369301.html
Copyright © 2020-2023  润新知