我们知道可以对一个数进行质因数分解,即
求最大公约数的方法是经典的欧几里得算法:
Code:
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
最大公约数和最小公倍数的关系:
Code:
void ex_gcd(int a,int b,int &x,int &y)
{
if (b==0) {x=1; y=0; return;}
ex_gcd(b,a%b,x,y);
int t=x; x=y; y=t-a/b*y;
}
利用扩展欧几里德算法可以解不定方程 a∗x+b∗y=n 。
模线性方程 a∗x≡b (mod p) 可以转化为 a∗x+p∗y=b 的形式,也可以用上述方法解决。
二分的思想.
Code:
inline int quick_power(int a,int b)
{
int ans=1;
for (int i=b;i;i>>=1,a=a*a%P)
if (i&1) ans=ans*a%P;
return ans;
}
二进制的思想,防止直接乘法爆掉.
Code:
int mul(int a,int b)
{
int ans=0;
for (int i=b;i;i>>=1,a=(a<<1)%p)
if (i&1) ans=(ans+a)%p;
return ans;
}
素数即只有1和自己两个约数的数。
线性筛素数Code:
inline void get_prime()
{
for (int i=2;i<=n;i++)
{
if (!flag[i]) prime[++prime[0]]=i,pos[i]=prime[0];
for (int j=1;j<=prime[0]&&i*prime[j]<=n;j++)
{
flag[i*prime[j]]=1; pos[i*prime[j]]=j;
if (i%prime[j]==0) break;
}
}
}
这个过程的复杂度是 O(n) 的,重点在于
线性筛欧拉函数Code:
inline void get_phi()
{
phi[1]=1;
for (int i=2;i<=n;i++)
{
if (!flag[i]) {prime[++prime[0]]=i; phi[i]=i-1;}
for (int j=1;j<=prime[0]&&i*prime[j]<=n;j++)
{
flag[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]*(prime[j]-1);
}
}
}
欧拉函数是小于 n 的数中与n**互质**的数的数目,我们用 φ(n) 来表示。
依旧是先进行质因数分解。
通式:
线性筛欧拉函数就是根据性质 2,4,5 来进行的。
定义:满足 a∗k≡1 (mod p) 的 k 值就是 a 关于 p 的乘法逆元,我们不妨用 invi 来表示 i 的乘法逆元。
ab mod p 就等价于 a∗invb mod p 。可以解决除法取模的问题。
乘法逆元的求法:
inv[1]=1;
for (int i=2;i<MAXN;i++) inv[i]=(p-p/i)*inv[p%i]%p;
以上为乘法逆元常用的 3 种求法。
高斯消元就是不断地消元然后回代,可用来解线性方程组,复杂度为 O(n3)
Code:
void gauss()
{
int y;
double t;
for (int i=1;i<=n;i++)
{
for (y=i;y<=n;y++)
if (fabs(a[y][i])>eps) break;
if (y>n) continue;
if (y!=i)
for (int j=1;j<=n+1;j++) swap(a[i][j],a[y][j]);
t=a[i][i];
for (int j=1;j<=n+1;j++) a[i][j]/=t;
for (int j=1;j<=n;j++)
if (j!=i)
{
t=a[j][i];
for (int k=1;k<=n+1;k++)
a[j][k]-=t*a[i][k];
}
}
}
高斯消元还可以用来解异或方程组,然后引入一个概念叫做自由元:在消元后若 fi,i=0 ,证明 i 是一个自由元,即 i 的取值不同会造成多解问题,在异或方程组中,由于取值只有 2 种,所以如果有 k 个自由元,解的个数为 2k 。常用枚举自由元的方式来计算不同的解。
Code:
inline void gauss()
{
int y;
for (int i=1;i<=n;i++)
{
for (y=i;y<=n;y++)
if (a[y][i]) break;
if (y>n) continue;
if (i!=y)
for (int j=1;j<=n+1;j++) swap(a[i][j],a[y][j]);
for (int j=1;j<=n;j++)
if (j!=i&&a[j][i])
for (int k=1;k<=n+1;k++) a[j][k]^=a[i][k];
}
}
void dfs(int x)
{
if (tot>=mn) return;
if (!x)
{
mn=min(mn,tot);
return;
}
if (a[x][x])
{
int t=a[x][n+1];
for (int i=x+1;i<=n;i++)
if (a[x][i]) t^=ans[i];
ans[x]=t;
if (t) tot++;
dfs(x-1);
if (t) tot--;
}
else
{
ans[x]=0; dfs(x-1);
ans[x]=1; tot++; dfs(x-1); tot--;
}
}
Lucas 定理是用来解决大组合数取模的。
即求 Cmn mod p ,其中 p 为质数。
Cmn % p=Cm/pn/p∗Cm%pn%p % p
线性筛莫比乌斯函数Code:
inline void prepare()
{
mobius[1]=1;
for (int i=2;i<MAXN;i++)
{
if (!flag[i]) prime[++prime[0]]=i,mobius[i]=-1;
for (int j=1;j<=prime[0]&&i*prime[j]<MAXN;j++)
{
flag[i*prime[j]]=1;
if (i%prime[j]==0)
{
mobius[i*prime[j]]=0;
break;
}
mobius[i*prime[j]]=-mobius[i];
}
}
}
参考资料:WC2016第二课堂宋新波讲稿
我们定义两个函数 f(n) 和 g(n) ,并且满足 f(n)=∑d∣ng(d) ,根据定义:
f(1)=g(1)
f(2)=g(1)+g(2)
f(3)=g(1)+g(3)
f(4)=g(1)+g(2)+g(4)
f(5)=g(1)+g(5)
f(6)=g(1)+g(2)+g(3)+g(6)
f(7)=g(7)
f(8)=g(1)+g(2)+g(4)+g(8)
于是我们可以得到:
g(1)=f(1)
g(2)=f(2)−f(1)
g(3)=f(3)−f(1)
g(4)=f(4)−f(2)
g(5)=f(5)−f(1)
g(6)=f(6)−f(3)−f(2)+f(1)
g(7)=f(7)−f(1)
g(8)=f(8)−f(4)
我们发现了什么?
g(n)=∑d∣nf(d)×?
而 ? 部分与 d 似乎没有什么关系,而与 nd 有关系。
我们定义一个新的函数,记为 μ(i)
则 g(n)=∑d∣nf(d)×μ(nd)
或者通过换元可以写成 g(n)=∑d∣nf(nd)×μ(d)
我们得到了公式:
f(n)=∑d∣ng(d)=>g(n)=∑d∣nf(nd)×μ(d)
莫比乌斯函数的定义
μ(d) 为莫比乌斯函数,定义如下:
①若 d=1 ,则 μ(d)=1
②若 d=P1×P2×...×Pn ,则 μ(d)=(−1)k
③其他情况 μ(d)=0
莫比乌斯函数的性质
①若 n=1 ,则 ∑d∣nμ(d)=1 ,否则 ∑d∣nμ(d)=0 .
可以用二项式定理来证明。
②莫比乌斯函数是积性函数
莫比乌斯反演的证明
证明: f(n)=∑d∣ng(d)=>g(n)=∑d∣nf(nd)×μ(d)
g(n)=∑d∣nf(nd)∗μ(d)
=∑d∣nμ(d)∑i∣ndg(i)
=∑i∣ng(i)∑d∣niμ(d)
①当 i=n 时, ∑d∣niμ(d)=1 , g(i)∑d∣niμ(d)=g(n)
②当 i 取其他值时, ∑d∣niμ(d)=0 , g(i)∑d∣niμ(d)=0
综上, g(n)=∑d∣nf(nd)μ(d)=g(n)
莫比乌斯反演的变形
f(i)=∑i∣d,d≤ng(d)=>g(i)=∑i∣d,d≤nf(d)μ(di)
其他写法: f(i)=∑⌊ni⌋d=1g(d∗i)=>g(i)=∑⌊ni⌋d=1f(d∗i)μ(d)
证明与莫比乌斯反演的证明类似,在此不赘述。
莫比乌斯反演的性质
f(n)=∑d∣ng(d) ,则“ g(n) 是积性函数”的充分必要条件是“ f(n) 是积性函数”。
常用技巧
变量替换,内层外移。
gcd(i,j)=1 等价于 ∑d∣gcd(i,j)μ(d) 。