1.首先明确积性函数
对于算术函数f(x)如果如果对于任意两个互质的正整数n,m,有f(nm)=f(n)*f(m),那么称该函数为积性函数。
如果对任意两个正整数n,m都满足f(nm)=f(n)*f(m),那么称为完全积性函数。
2.欧拉函数的概念
φ(n);表示不大于n,并且和n互质的数的个数(包括1)。
φ(1)=1;
3.
定理1:
如果f(x)为积性函数,通过质因子分解可以表示为
x=p1^a1+ p2 ^a2+p3 ^a3…+pn ^an
那么f(x)=f(p1^a1)f(p2 ^a2)…f(pn ^an),
根据积性函数的定义容易得到。
定理2:
通过质因子分解后,
n=p1^a1p2 ^a2p3 ^a3*…pk ^ak
欧拉函数的计算公式为,
φ(n)=n(1-1/p1) * (1-1/p2)…(1-1/pk)
理解:
对于n以内p1的倍数是均匀分布的,所以1~p1内p1的倍数所占的比例是1/p1,又因为n为p1的整数倍,所以1 ~ n内p1的倍数的比例也为1/p1,所以1 ~n 内不是p1的倍数的数的个数为 n*(1-1/p1)。
同理,其余均是如此。
连续相乘后的结果就表示1~n中,不是p1,p2…pk中任意一个数的倍数的数的个数,即与n互质的数的个数。
推论:
当n为奇数时,φ(2*n)=φ(n)
定理3:
如果p为素数,那么φ(p )=p-1。
反之也成立,二者互为充要条件。
φ ( p)=p*(1-1/p)=p-1。
定理4:
设p为素数,a是一个正整数,那么φ(p^a)=p ^a-p ^(a-1).
定理5:
设n,m是两个互素的正整数,那么φ(nm)=φ(n)*φ(m)。
积性函数的性质
定理6:
对于一个大于2的正整数x,φ(x)为偶数。
4.欧拉定理:
对于两个互质的正整数a,m(m>=2),有a^φ(m)=1(mod m)
特殊情况:
m为素数时,有费马小定理 a^(m-1)=1(mod m)
5.如何求欧拉函数
(1),对单个数求O(sqrt(n))
基本思想是:欧拉函数的计算公式。质因子分解。
#include <bits/stdc++.h>
using namespace std;
int euler_fun(int x)
{
int res=x;
for(int i=2;i*i<=x;i++)
{
if(x%i==0)
{
res-=(res/i);
while(x%i==0)
x/=i;
}
}
if(x>1)
res-=(res/x);
return res;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
printf("%d\n",euler_fun(n));
}
return 0;
}
(2)线性筛欧拉函数(欧拉筛筛欧拉函数)
首先要对欧拉筛有了解,同样是利用欧拉函数的性质和计算公式
void euler(int n)
{
phi[1]=1;//1要特判
for (int i=2;i<=n;i++)
{
if (flag[i]==0)//这代表i是质数
{
prime[++num]=i;
phi[i]=i-1;
}
for (int j=1;j<=num&&prime[j]*i<=n;j++)//经典的欧拉筛写法
{
flag[i*prime[j]]=1;//先把这个合数标记掉
if (i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];//若prime[j]是i的质因子,
//则根据计算公式,i已经包括i*prime[j]的所有质因子
break;//经典欧拉筛的核心语句,这样能保证每个数只会被自己最小的因子筛掉一次
}
else phi[i*prime[j]]=phi[i]*phi[prime[j]];//利用了欧拉函数是个积性函数的性质
}
}
}
当i%prime[j]==0时,iprime[j]和i的质因子相同,i是prime[j]的倍数,根据计算公式,只要把公式的第一项n换成iprime[j]即可,其它的不变,因此phi[i*prime[j]]=phi[i]*prime[j]。
(3)递推求欧拉函数O(nln n)
基本思想:利用上述定理3,利用素数。首先把所有的数的欧拉函数值赋本身,然后从小到大进行遍历,如果一个数的欧拉函数的值=这个数本身,那么这个数为一个素数,对它进行处理,同时对它的倍数根据计算公式进行处理,即可。
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+5;
int phi[N];
void euler()
{
for(int i=1;i<N;i++)
phi[i]=i;//一开始赋初值
for(int i=2;i<N;i++)//遍历
{
if(phi[i]==i)//i为素数
{
for(int j=i;j<N;j+=i)//对范围内的i的倍数进行处理
phi[j]=phi[j]/i*(i-1);
}
}
}
int main()
{
euler();
for(int i=1;i<=100;i++)
printf("%d:%d\n",i,phi[i]);
return 0;
}
poj2407
poj1284
poj2478(模板题)
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
const int N=1e6+5;
typedef long long ll;
int phi[N];
bool vis[N];
vector<int>prime;
void euler()
{
prime.clear();
memset(vis,false,sizeof(vis));
for(int i=2;i<=N-5;i++)
{
if(!vis[i])
{
prime.push_back(i);
phi[i]=i-1;
}
for(int j=0;j<prime.size()&&i*prime[j]<=N-5;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else
phi[i*prime[j]]=phi[i]*phi[prime[j]];
}
}
}
int main()
{
int n;
euler();
while(scanf("%d",&n),n)
{
ll ans=0;
for(int i=2;i<=n;i++)
ans+=phi[i];
printf("%lld\n",ans);
}
return 0;
}
poj3090
关键在于发现欧拉函数的应用。
可以在纸上画出前几个图,发现,可以画边的点的纵横坐标的关系为欧拉函数的关系(只看水平方向),即横坐标为于纵坐标互质的数。(n=1时除外),这样就可以转化为欧拉函数求解,最后只要求和,然后加上竖直方向即可。
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int N=1e3+5;
int phi[N];
vector<int>prime;
bool vis[N];
void euler()
{
memset(vis,false,sizeof(false));
prime.clear();
for(int i=2;i<=N-5;i++)
{
if(!vis[i])
{
prime.push_back(i);
phi[i]=i-1;
}
for(int j=0;j<prime.size()&&i*prime[j]<=N-5;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else
phi[i*prime[j]]=phi[i]*phi[prime[j]];
}
}
}
int main()
{
int n,c;
euler();
scanf("%d",&c);
for(int i=1;i<=c;i++)
{
scanf("%d",&n);
long long ans=3;
for(int j=2;j<=n;j++)
ans+=(2*phi[j]);
printf("%d %d %lld\n",i,n,ans);
}
return 0;
}
poj3696
大概思路:
利用(10^p - 1)8/9刚好是全为8的数字,所以有方程(10 ^p -1)8/9= L x
所以,10^p -1=Lx9/8;
令m=9L/gcd(8,L),
则 10^p -1=m*x
所以有10^p=1(mod m)
由欧拉定理,有当p=φ(m)时,方程成立。
有因为要求最小的解,所以对φ(m)进行质因子分解,对于某一个质因子a,进行p/=a,直到把p代入方程不成立或p%a!=0,结束。
对所有的质因子进行这样的遍历。
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=1e3+5;
ll n,m;
ll p[N]={0};
ll gcd(ll a,ll b)
{
return (b==0)?a:gcd(b,a%b);
}
ll euler(ll x)
{
ll res=x;
for(ll i=2;i*i<=x;i++)//这个地方如果用int,会超时
{
if(x%i==0)
{
res-=(res/i);
while(x%i==0)
x/=i;
}
}
if(x>1)
res-=(res/x);
return res;
}
ll mul(ll a,ll b)
{
ll res=0;
a%=m;
b%=m;
while(b)
{
if(b&1)
{
res+=a;
if(res>=m)
res-=m;
}
a<<=1;
if(a>=m)
a-=m;
b>>=1;
}
if(res>=m)
res-=m;
return res;
}
ll f_pow(ll a,ll b)
{
ll res=1;
while(b)
{
if(b&1)
res=mul(res,a)%m;
a=mul(a,a)%m;
b>>=1;
}
return res%m;
}
void fun(ll x)
{
p[0]=0;
ll t=x;
for(ll i=2;i*i<=x;i++)
{
if(t%i==0)
{
p[++p[0]]=i;;
while(t%i==0)
t/=i;
}
}
if(t>1)
p[++p[0]]=t;
}
void solve(ll x)
{
fun(x);//求出所有质因子
for(int i=1;i<=p[0];i++)
{//printf(" %d\n",p[i]);
while(1)
{
x/=p[i];
if(f_pow(10,x)!=1)
{
x*=p[i];
break;
}
else if(x%p[i])
break;
}
}
printf("%lld\n",x);
}
int main()
{
int cas=0;
while(scanf("%lld",&n),n)
{
printf("Case %d: ",++cas);
m=n/gcd(n,8)*9;
if(gcd(10,m)!=1)
puts("0");
else
solve(euler(m));
}
return 0;
}
poj3358
首先要知道如何把小数部分转化为二进制表示,
对小数部分不断乘2,直到整数位大于1,此时二进制取1,然后舍弃整数位,继续对小数位操作。
如:
0.1:
0.12=0.2 0
0.22=0.4 0
0.42=0.8 0
0.82=1.6 1
0.62=1.2 1
0.22=0.4 0
…
#include <cstdio>
#include <cstring>
using namespace std;
int p,q,mod;
int fac[1005]={0};
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
int euler(int x)
{
int res=x;
for(int i=2;i*i<=x;i++)
{
if(x%i==0)
{
res-=(res/i);
while(x%i==0)
x/=i;
}
}
if(x>1)
res-=(res/x);
return res;
}
void factor(int a)
{
int ta=a;
fac[0]=0;
for(int i=2;i*i<=a;i++)
{
if(ta%i==0)
{
fac[++fac[0]]=i;
while(ta%i==0)
ta/=i;
}
}
if(ta>1)
fac[++fac[0]]=ta;
}
int m_pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)
res=(long long)res*a%mod;
a=(long long)a*a%mod;//wa会超int
b>>=1;
}
return res%mod;
}
int solve(int x)
{
factor(x);
for(int i=1;i<=fac[0];i++)
{
while(1)
{
x/=fac[i];
if(m_pow(2,x)!=1)
{
x*=fac[i];
break;
}
else if(x%fac[i])
break;
}
}
return x;
}
int main()
{
int cas=0;
while(scanf("%d/%d",&p,&q)!=EOF)
{
int d=gcd(p,q);
int p1=p/d;
int q1=q/d;
int pos=0;//开始位置
while(q1%2==0)
{
q1/=2;
pos++;
}
mod=q1;
int x=euler(q1);//循环节为x的质因子
printf("Case #%d: %d,%d\n",++cas,pos+1,solve(x));
}
return 0;
}
更多题目
hdu3501
自己推了好久,还是一直错!
借鉴别人的思路。自己还是想不到。
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
ll euler(ll x)
{
ll res=x;
for(int i=2;(ll)i*i<=x;i++)
{
if(x%i==0)
{
res-=(res/i);
while(x%i==0)
x/=i;
}
}
if(x>1)
res-=(res/x);
return res;
}
int main()
{
ll n;
while(scanf("%lld",&n),n)
{
ll ans=(n-1)*n/2;
printf("%lld\n",(ans-(n*euler(n)/2))%mod);
}
return 0;
}
hdu2588
要求[1,n]中与n的gcd>=m的数的个数,
令gcd(n,x)=a;
其中n=ab,x=ac。
有(b,c)=1,否则(n,x)!=a。
因而只要让a>=m,求出x的个数,即可。
即求满足a>=m的b的欧拉函数的和
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
int n,m;
int euler(int x)
{
int res=x;
for(int i=2;i*i<=x;i++)
{
if(x%i==0)
{
res-=(res/i);
while(x%i==0)
x/=i;
}
}
if(x>1)
res-=(res/x);
return res;
}
int solve()
{
int tn=n;
int ans=0;
for(int i=1;i*i<=n;i++)
{
if(n%i==0)
{
if(i>=m)
ans+=euler(n/i);
if(n/i>=m&&(n/i)!=i)
ans+=euler(i);
}
}
return ans;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
printf("%d\n",solve());
}
return 0;
}
Star SPOJ - STARSBC
phi[n]/2
用int会超时,改成long long 就过了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll euler(ll x)
{
ll res=x;
for(ll i=2;i*i<=x;i++)
{
if(x%i==0)
{
res-=(res/i);
while(x%i==0)
x/=i;
}
}
if(x>1)
res-=(res/x);
return res;
}
int main()
{
ll n;
while(scanf("%lld",&n)==1)
{
printf("%lld\n",euler(n)/2);
}
return 0;
}
hdu4983
大概思路就是对x的可能取值,进行分析,以使得(n-a)/x和x互质。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int mod=1e9+7;
typedef long long ll;
ll n,k;
ll euler(ll x)
{
ll res=x;
for(ll i=2;i*i<=x;i++)
{
if(x%i==0)
{
res-=(res/i);
while(x%i==0)
x/=i;
}
}
if(x>1)
res-=(res/x);
return res;
}
ll solve(ll x)
{
ll ans=0;
for(ll i=1;i*i<=x;i++)
{
if(x%i==0)
{
ll ta=euler(i);
if(i*i==x)
ans+=(ta*ta);
else
ans+=(2*ta*euler(x/i));
}
ans%=mod;
}
return ans;
}
int main()
{
while(scanf("%lld%lld",&n,&k)!=EOF)
{
if(n==1)//特判,易错
puts("1");
else if(k>2)
puts("0");
else if(k==2)
puts("1");
else
printf("%lld\n",solve(n)%mod);
}
return 0;
}
hdu5152
线段树+欧拉函数
以后再补