算法原理:
int prime[maxn];
bool vis[maxn];//初始0,标记合数
void euler(int n)
{
for(int i=2;i<=n;i++)
{
if(!vis[i])prime[++prime[0]]=i;
for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++)
{
vis[i*prime[j]]=true;//筛去合数
if(i%prime[j]==0)break;
}
}
}
若函数
特别的,若
若
若
若
(因为质数和其自己的
欧拉函数
除数函数
(特别的,
二者均为非完全积性函数。
莫比乌斯函数
仔细观察不难发现2是4的子集关系,故性质1、3、4称作欧拉函数三性质,简称欧拉函数性。
假设一个积性函数
设合数
显然此时问题转化成了求某质数的最小质因子,而欧拉筛恰好就是根据这个过程来做到线性筛除的。
int prime[maxn];
bool vis[maxn];
int f[maxn];
int g[maxn];
void euler(int n)
{
g[0]=0;
g[1]=0;
for(int i=2;i<=maxn;i++)
{
if(!vis[i])
{
prime[++prime[0]]=i;
g[i]=i;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++)
{
vis[i*prime[j]]=true;//合数标记
if(i%prime[j])
{
g[i*prime[j]]=g[i]*prime[j];
break;
}
g[i*prime[j]]=prime[j];
}
}
}
int query(int x)
{
if(x==g[x])return f[x];
return query(x/g[x])*query(g[x]);
}
事实上,对于
换言之,如果一个积性函数能够满足以下三条性质:
则均可使用线性筛实现线性求解。
下列是欧拉函数筛法例子
int prime[maxn];
bool vis[maxn];//初始0,标记合数
int phi[maxn];
void euler(int n)
{
for(int i=2;i<=n;i++)
{
if(!vis[i])prime[++prime[0]]=i,phi[i]=i-1;\\性质1
for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++)
{
vis[i*prime[j]]=true;\\筛去合数
if(i%prime[j]==0)
{
phi[i*prime[j]]=prime[j]*phi[i];\\性质3
break;
}
phi[i*prime[j]]=phi[prime[j]]*phi[i];\\性质2
}
}
}
下例是
综上所述,可以使用欧拉筛解决。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+10;
int num[maxn],f[maxn],prime[maxn];
bool vis[maxn];
void init()
{
f[0]=f[1]=1;
for(int i=2;i<=2e5;i++)
{
if(!vis[i])
{
prime[++prime[0]]=i;
vis[i]=1;
f[i]=i;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=2e5;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
f[i*prime[j]]=f[i/prime[j]];
break;
}
f[i*prime[j]]=f[i]*f[prime[j]];
}
}
return;
}
int a[maxn];
signed main()
{
init();
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],num[a[i]]++;
long long ans=0;
for(int i=1;i<=2e5;i++)
{
if(!num[i])continue;
for(int p=1;p<=450&&p*p*f[i]<=2e5;p++)
{
if(p*p*f[i]!=i)
ans+=num[i]*num[p*p*f[i]];
}
}
ans/=2;
for(int i=0;i<=2e5;i++)
{
if(num[i]>1)
ans+=num[i]*(num[i]-1)/2;
}
ans+=num[0]*(n-num[0]);
cout<<ans<<endl;
system("pause");
return 0;
}
欧几里得算法
int gcd(int x,int y)
{return y==0? x : gcd(y,x%y); }
大整数求模运算时间复杂度过高,建议换用更相减损术(最坏情况
int gcd(int x,int y)
{
if(x>y)swap(x,y);
return y==0?x:gcd(y,y-x);
}
对于
若
否则若
否则若
否则
最小公倍数算法
求解方程
算法原理:
void exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;y=0;return;
}
exgcd(b,a%b,y,x);
y-=(a/b)*x;
return;
}
此时求解到的
证明,
扩展欧拉定理应用于对超大整数快速幂求模的优化降幂运算,来大幅度降低时间复杂度
#define int long long
int prime[maxn];
bool vis[maxn];//初始0,标记合数
int phi[maxn];
void euler(int n)
{
for(int i=2;i<=n;i++)
{
if(!vis[i])prime[++prime[0]]=i,phi[i]=i-1;\\性质1
for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++)
{
vis[i*prime[j]]=true;\\筛去合数
if(i%prime[j]==0)
{
phi[i*prime[j]]=prime[j]*phi[i];\\性质3
break;
}
phi[i*prime[j]]=phi[prime[j]]*phi[i];\\性质2
}
}
}
int qpow(int a, int n, int MOD) // 快速幂
{
int ans = 1;
while (n)
{
if (n & 1)
ans = ans * a % MOD;
n >>= 1;
a = a * a % MOD;
}
return ans;
}
int Ex_euler_T(int a,int n)
{
if(__gcd(a,MOD)==1)
{
return qpow(a,b%phi[m]);
}
else
{
if(b<phi[m])
{
return qpow(a,b);
}
return qpow(a,(b%phi[m])+phi[m]);
}
}
#undef int
如何求解分数的模?换言之,已知
扩展欧几里得定理求解
方程极易转化为
int exgcd(int a,int b int &x,int &y)
{
if(b==0)
{
x=1;y=0;return;
}
exgcd(b,a%b,y,x);
y-=(a/b)*x;
}
一般的我们需要最小的正整数解:
int invq(int a,int mod)
{
int x,y;
exgcd(a,mod,x,y);
return (x%mod+mod)%mod;
}
费马小定理求解
这个是最常用的,一般题目都会让对质数求模。
int invq(int a)
{
return quickpow(a,mod-2);
}
逆元运算是分数意义的求模,在整数域定义了实数意义上的倒数运算,其基本性质和倒数没有区别。根据此可由前缀积实现线性求
解方程:
今有物不知几何,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?
一般的,中国剩余定理可以求解如下线性同余方程组
其中所有
#include <bits/stdc++.h>
using namespace std;
#define int long long
void Exgcd(int a, int b, int &x, int &y)
{
if (b == 0)
{
x = 1, y = 0;
return;
}
Exgcd(b, a % b, y, x);
y -= (a / b) * x;
return;
}
const int maxn = 101;
int m;
int n[maxn], a[maxn]; // 对a取模余数是b
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> m;
int N = 1;
for (int i = 1; i <= m; i++)
{
cin >> n[i] >> a[i];
N *= n[i];
}
int ans = 0;
for (int i = 1; i <= m; i++)
{
int tar = N / n[i];
int x, y;
Exgcd(tar, n[i], x, y);
x = (x % n[i] + n[i]) % n[i];
(ans += ((tar * x) % N * (a[i] % N))) %= N;
}
cout << (ans + N) % N << endl;
system("pause");
return 0;
}
此时
任取两组方程
那么可以将等式转化为
否则可计算出
需要注意的是,在线性同余方程组求解中,
#define int long long
int quickmul(int a,int b,int mod)
{
int e=0;
while(b)
{
if(b&1)(e+=a)%=mod;
(a<<=1)%=mod;
b>>=1;
}
return e;
}
#undef int
#include <bits/stdc++.h>
using namespace std;
#define int long long
int quickmul(int a, int b, int mod)
{
while (a < 0)
a += mod;
while (b < 0)
b += mod;
if (a < b)
swap(a, b);
int e = 0;
while (b)
{
if (b & 1)
(e += a) %= mod;
(a <<= 1) %= mod;
b >>= 1;
}
return e;
}
void exgcd(int a, int b, int &x, int &y)
{
if (b == 0)
{
x = 1, y = 0;
return;
}
exgcd(b, a % b, y, x);
y -= (a / b) * x;
}
const int maxn = 500001;
int a[maxn], m[maxn]; // 对m取模余数是a
int n;
int ExCRT()
{
int mod = m[1], ans = a[1];
for (int i = 2; i <= n; i++)
{
int b1 = mod, b2 = m[i], c = __gcd(b1, b2), minus = (a[i] - ans % b2 + b2) % b2;
if (minus % c != 0)
return -1;
b1 /= c, b2 /= c, minus /= c;
int x, y;
exgcd(b1, b2, x, y);
x = quickmul(x, minus, b2);
ans += x * mod;
mod *= b2;
ans = (ans % mod + mod) % mod;
}
return ans;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> m[i] >> a[i];
}
cout << ExCRT() << endl;
system("pause");
return 0;
}
亦或写作
long long C(long long n,long long m,long long mod)
{
if(m>n&&n==0)return 0;
if(m==0)return 1;
return ((frac[n]*invq[m]%mod)*invq[n-m])%mod;
}
//阶乘的值和逆元预处理是线性的递推
long long Lucas(long long n, long long m, long long p)
{
if (m == 0)
return 1;
return (C(n % p, m % p, p) * Lucas(n / p, m / p, p)) % p;
}
请我喝杯咖啡吧~