质约数

判断质数 $O(\sqrt{n})$

1
2
3
4
5
6
7
8
9
10
11
12
bool isPrime(int x)
{
if (x<2) return false;
for (int i=2;i<=x/i;i++)
{
if (x%i==0)
{
return false;
}
}
return true;
}

分解质因数 $O(\log{n}) O(\sqrt{n})$

最多只包含一个大于 $\sqrt{n}$ 的 质因数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void divide(int n)
{
for(int i=2;i<=n/i;i++)
{
if(n%i==0)
{
int s=0;
while(n%i==0)
{
n=n/i;
s++;
}
cout<<i<<' '<<s<<endl;
}
}
if(n>1) cout<<n<<' '<<'1'<<endl;
}

筛质数

  • 埃筛

  • 找出小于等于 $n$ 的质数

  • $O(n\log{\log{n}})$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void get_prime(int n)
{
for(int i=2;i<=n;i++)
{
if(!pd[i])
{
prime[cnt++]=i;
for(int j=i;j<=n;j+=i)
{
pd[j]=true;
}
}
}
}
  • 线筛 $O(n)$
1
2
3
4
5
6
7
8
9
10
11
12
13
void get_prime(int n)
{
for(int i=2;i<=n;i++)
{
if(!pd[i]) prime[cnt++]=i;

for(int j=0;prime[j]<=n/i;j++)
{
pd[prime[j]*i]=true;
if(i%prime[j]==0) break;
}
}
}

求约数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<int> get_div(int n)
{
vector<int> res;

for(int i=1;i<=n/i;i++)
{
if(n%i==0)
{
res.push_back(i);
if(i!=n/i)
{
res.push_back(n/i);
}
}
}
sort(res.begin(),res.end());
return res;
}

约数个数

对于一个正整数$n$,它的约数个数可以通过以下步骤来计算:

  1. 将$n$分解质因数,得到形如$n = p_1^{\alpha_1} \cdot p_2^{\alpha_2} \cdot … \cdot p_k^{\alpha_k}$的表达式,其中$p_i$为质数,$\alpha_k$为对应的指数。
  2. 约数个数即为:
    $$\prod_{i=1}^{k} (1 + \alpha_i)=(1 + \alpha_1) \cdot (1 + \alpha_2) \cdot … \cdot (1 + \alpha_k)$$

例如,对于$n = 12$,它的约数为1、2、3、4、6、12,共有6个约数。我们可以将12分解为$12 = 2^2 \cdot 3^1$,然后计算约数个数为$\prod_{i=1}^{2} (\alpha_i + 1) = (1 + 2) \cdot (1 + 1) = 3 \cdot 2 = 6$。

  • int 范围内一个数字的约数最多约1500个

  • 可以用哈希表存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        cin>>x;
    for(int i=2;i<=x/i;i++) //求 x 的分解质因数,存到 map 里面
    {
    if(x%i==0)
    {
    while (x%i==0)
    {
    x=x/i;
    primes[i]++;
    }
    }
    }
    if(x>1) primes[x]++;


    ll ans=1;
    for(auto prime:primes)
    {
    ans=ans*(prime.second+1)%mod;
    }

约数之和

  • 假设正整数 $n$ 的质因数分解为:$n = p_1^{\alpha_1} \cdot p_2^{\alpha_2} \cdot … \cdot p_k^{\alpha_k}$

  • 那么 $n$ 的所有约数之和可以表示为:

$$\sigma(n) = (1 + p_1 + p_1^2 + … + p_1^{\alpha_1})\cdot (1 + p_2 + p_2^2 + … + p_2^{\alpha_2}) \cdot … \cdot (1 + p_k + p_k^2 + … + p_k^{\alpha_k})$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    cin>>x;
for(int i=2;i<=x/i;i++) // x 的分解质因数
{
if(x%i==0)
{
while (x%i==0)
{
x=x/i;
primes[i]++;
}
}
}
if(x>1) primes[x]++;


ll ans=1;
for(auto prime:primes)
{
int p=prime.first;
int a=prime.second;
ll t=1;
while (a--)
{
t=t*(p+1)%mod;
}
ans=ans*(t)%mod;
}
cout<<ans<<endl;

最大公约数

1
2
3
4
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}

欧拉函数

  • 欧拉函数表示 1~$n$ 中互质的数的个数
    (互质是公约数只有1的两个整数)

  • $n$ 的质因数分解: (n = p_1^{\alpha_1} \cdot p_2^{\alpha_2} \cdot … \cdot p_k^{\alpha_k})

  • $n$ 的欧拉函数 (\phi(n)) 可以用以下公式表示:

$$\phi(n) = n \times \left(1 - \frac{1}{p_1}\right) \times \left(1 - \frac{1}{p_2}\right) \times \ldots \times \left(1 - \frac{1}{p_k}\right)$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void get(int n)     //求n的欧拉函数
{
int ans=n;
for(int i=2;i<=n/i;i++)
{
if(n%i==0)
{
while (n%i==0)
{
n=n/i;
} //找出质因数 i
ans=ans/i*(i-1);
}
}
if(n>1) ans=ans/n*(n-1);
cout<<ans<<endl;
}

线筛求欧拉函数 (可用来求 连续 数字的欧拉函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void euler(int n)   //欧拉
{
for(int i=2;i<=n;i++)
{
if(!pd[i])
{
phi[i]=i-1;
primes[cnt++]=i;
}

for(int j=0;primes[j]<=n/i;j++)
{
pd[primes[j]*i]=true;

if(i%primes[j]==0) //pj是i的最小质因数
{
phi[i*primes[j]]=primes[j]*phi[i];
break;
}

phi[i*primes[j]]=(primes[j]-1)*phi[i]; //pj不是i的质因数,pj是i*primes[j]的最小质因数
}
}
}

快速幂

1
2
3
4
5
6
7
8
9
10
11
ll qmi(int a,int k,int mod)     //快速计算a^k%mod
{
ll res=1;
while(k)
{
if(k&1) res=res*a%mod;
k>>=1; //去掉最后一位
a=(ll)a*a%mod; //2 -> 4 -> 8 -> 16 ...
}
return res;
}

快速幂求逆元

推导过程:

如果p是一个素数,而a是不是p的倍数的整数,则有费马小定理:

$$a^{p-1} \equiv 1 \ (\text{mod} \ p)$$

我们要找到一个数 “x”,满足以下条件:

$$a \cdot x \equiv 1 \ (\text{mod} \ m)$$

  • 步骤1:计算 $a^{m-2} \ (\text{mod} \ m)$
    我们将 “a” 的幂乘以 $m-2$ 并将结果对 “m” 取模。这基于费马小定理,该定理指出,如果 “a” 和 “m” 互质(gcd(a, m) = 1),那么 $a^{m-1} \equiv 1 \ (\text{mod} \ m)$。将同余式的两边都乘以 $a^{-1}$,我们得到 $a^{m-2} \equiv a^{-1} \ (\text{mod} \ m)$。

  • 步骤2:令 $x = a^{m-2} \ (\text{mod} \ m)$
    步骤1的结果给出了我们所需的模意义下的乘法逆元 “x”。

因此,模意义下的乘法逆元公式为:

$$a^{-1} \equiv a^{m-2} \ (\text{mod} \ m)$$

注意特判,即 $a=m$ 时无解且本公式只在 $q$ 为质数的时候成立

1
ll qmi(int a,int mod-2,int mod)

扩展欧几里得

裴蜀定理:

$$ax + by = \text{gcd}(a, b)$$

exgcd(a, b, x, y) = exgcd(b, a % b, y, x)

$$b \times y + (a - \lfloor \frac{a}{b} \rfloor \times b) \times x = d$$

$$b \times (y - \lfloor \frac{a}{b} \rfloor \times x) + a \times x = d$$

∴ 更新 y 为 (y - \lfloor \frac{a}{b} \rfloor \times x)

1
2
3
4
5
6
7
8
9
10
11
12
13
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}

int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}

线性同余方程

$ax \equiv b \pmod{m}$

  1. 利用拓展欧几里得算法求解
    $ax≡b\pmod{m} \Rightarrow ax+my≡b\pmod{m}$

  2. 仅$b=kd$ 时候有解$(d=gcd(a,m))$,代入得
    $$a(\frac{x}{k})+m(\frac{y}{k})=b$$

  3. 通过exgcd可算出 $(\frac{x}{k})$ 的值$=x’$,则$x=k*x’$

线性求逆元

求 1~p 之间所有数关于 $\pmod{p}$ 的逆元

1
2
3
4
5
6
insum[1]=1;

for(int i=2;i<=p;i++)
{
insum[i]=(p-p/i)*insum[p%i]%p;
}

高斯消元

线性代数:增广矩阵化简为最简阶梯矩阵….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

const int N=110;
const double eps=1e-8;//浮点数判断 0
double a[N][N];
int n;
// c列
// r行

int guess()
{
int c,r; //找当前左上角的列、行
for(c=0,r=0;c<n;c++)
{
int t=r;
for(int i=r;i<n;i++) //找出首位最的行 (a[r][c]为左上角的那个值)
{
if(fabs(a[t][c])<fabs(a[i][c]))
{
t=i;
}
}

if(fabs(a[t][c])<eps) continue;

for(int j=c;j<=n;j++) //把最大行放最上面
{
swap(a[r][j],a[t][j]);
}

for(int j=n;j>=c;j--) //首项为1,右边跟着变
{
a[r][j]=a[r][j]/a[r][c];
}

for(int i=r+1;i<n;i++) //下方都减为0
{
if(fabs(a[i][c])>eps)
{
for(int j=n;j>=c;j--)
{
a[i][j]=a[i][j]-a[r][j]*a[i][c];
}
}
}

r++;
}

if(r<n) //第r行全是0
{
for(int i=r;i<n;i++)
{
if(fabs(a[i][n])>eps) return 2;//无解
}
return 1; //无穷解
}

for(int i=n-1;i>=0;i--) //对于每一层,减去下面的数字(*倍率),从而求解
{
for(int j=i+1;j<n;j++)
{
a[i][n]=a[i][n]-a[j][n]*a[i][j];
}
}
return 0; //有唯一解
}

int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<=n;j++)
{
cin>>a[i][j];
}
}
int t=guess();
if(t==0)
{
for(int i=0;i<n;i++)
{
printf("%.2f\n",a[i][n]);
}
}else
{
cout<<"No Solution"<<endl;
}
return 0;
}

组合数

  • 组合数公式:

$$C_{a}^{b} = \frac{a!}{b! \cdot (a - b)!}=C_{a-1}^{b}+C_{a-1}^{b-1}$$

递归

  • $C_{a}^{b}=C(a,b)=C(a-1,b-1)+C(a-1,b)$

  • $N \approx 2000$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void init()
{
for(int i=0;i<N;i++)
{
for(int j=0;j<=i;j++) //注意调节是j<=i
{
if(j==0)
{
c[i][j]=1;
}else
{
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
}
}
}
}

预处理

  • $N \approx 10^5$

fac[i] 为 $i$ 的阶乘, infac[i] 为 $i$ 阶乘的逆元

$$C_{a}^{b} = \frac{a!}{b! \cdot (a - b)!}=fac[a]*infac[b]*infac[a-b]$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ll qmi(int a,int k,int q)   //用来算逆元
{
ll res=1;
while (k)
{
if(k&1)
{
res=res*a%q;
}
k>>=1;
a=(ll)a*a%q;
}
return res;
}


fac[0]=1;
infac[0]=1;
for(int i=1;i<=N;i++)
{
fac[i]=(ll)fac[i-1]*i%mod;
infac[i]=infac[i-1]*qmi(i,mod-2,mod)%mod;//i的 逆元
}

Lucas 卢卡斯定理

  • $N \approx 10^{18} $

$$C_{a}^{b} \equiv C_{a\pmod{p}}^{b\pmod{p}}*C_\frac{b}{p}^\frac{a}{p}\pmod{p}$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<iostream>
using namespace std;
typedef long long ll; //开 long long 避免爆 int

int p;

ll qmi(int a,int k,int p) //快速幂
{
ll res=1;
while (k)
{
if(k&1) res=(ll)res*a%p;
k>>=1;
a=(ll)a*a%p;
}
return res;
}


ll C(ll a,ll b) //直接计算C(a,b)
{
if(b>a) return 0; //边界条件

ll res=1;
for(ll i=1,j=a;i<=b;i++,j--)
{
res=(ll)res*j%p;
res=(ll)res*qmi(i,p-2,p)%p;
}
return res;
}

ll Lucas(ll a,ll b) //用Lucas定理和组合数公式计算
{
if(a<p&&b<p)
{
return C(a,b);
}else
{
return C(a%p,b%p)*Lucas(a/p,b/p)%p;
}
}

int main()
{
int T;
cin>>T;
int a,b;
while (T--)
{
cin>>a>>b>>p;
cout<<Lucas(a+b,b)%p<<endl;
}
return 0;
}

高精度 (待补)

1
//```

卡特兰数

合法路径数:

$$C_{2n}^{n} - C_{2n}^{n-1} = \frac{(2n)!}{n! \times n!} - \frac{(2n)!}{(n+1)!\times(n-1)!} $$

$$= \frac{(2n)! \times (n+1)}{n!\times (n+1)!}- \frac{(2n)! \times n}{n!\times (n+1)!}$$

$$= \frac{(2n)!}{n! \times (n+1)!} =\frac{1 }{n+1}\times \frac{(2n)! }{(n)!\times(n)!}$$

$$=\frac{C_{2n}^{n} }{n+1}$$

容斥

$|A_1 \cup A_2 \cup \ldots \cup A_n| = \sum_{i=1}^{n} |A_i| - \sum_{1 \leq i < j \leq n} |A_i \cap A_j| + \sum_{1 \leq i < j < k \leq n} |A_i \cap A_j \cap A_k| - \ldots + (-1)^{n-1} |A_1 \cap A_2 \cap \ldots \cap A_n|$

例:计算 1~n 中能被质数数组 p[] 中某个(些)元素整除的数的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<iostream>
using namespace std;

typedef long long ll;
const int N=1010;
int p[N];

int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>p[i];
}

int res=0;
for(int i=1;i<1<<m;i++) //i<(1<<m),i<2^m
{
ll prims=1;
int cnt=0;
bool flag=true; //判断该数是否可以被小于n的数整除
for(int j=0;j<m;j++)
{
if(i>>j&1)
{
if(prims*p[j]>n)
{
flag=false;
break;
}
prims=prims*p[j];
cnt++;
}
}
if(flag)
{
if(cnt%2==0) // n/prims为小于n的数中可以被primes整除的个数
{
res-=n/prims;
}else
{
res+=n/prims;
}
} //1-2+3-4+5-6+7······
}
cout<<res<<endl;
return 0;
}

Nim 游戏

$S_i$ 表示第 $i$ 堆石头数量($\bigoplus$:异或)

  • 先手必胜:$S_1\bigoplus S_2\bigoplus S_3\bigoplus S_4······S_n≠0$

  • 先手必败:$S_1\bigoplus S_2\bigoplus S_3\bigoplus S_4······S_n=0$

注:任何数和0异或都等于原数

SG 函数

定义 $mex(S)$ 为求出不属于集合 $S$ 的最小非负整数的运算,即:$mex(S)=min[x]$, 其中 $x$ 属于自然数,且 $x$ 不属于集合 $S$
(结束为0)

对于一个有向图 $D$:

  • 先手必胜:$S(D)≠0$ (必有一条路可以走到$S(D)=0$)

  • 先手必败:$S(D)=0$

对于 $n$ 个有向图D:

  • 先手必胜:$S(D_1)\bigoplus S(D_2)\bigoplus S(D_3)\bigoplus ······S(D_n)\bigoplus ≠0$

  • 先手必败:$S(D_1)\bigoplus S(D_2)\bigoplus S(D_3)\bigoplus ······S(D_n)\bigoplus =0$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<iostream>
#include<unordered_set> //集合
#include<cstring>
using namespace std;

const int N=150;
int n,m;
int sg[N];
int nums[N];

int SG(int x) //计算并返回 sg[x]
{
if(sg[x]!=-1) return sg[x];

unordered_set<int> S;
for(int i=0;i<m;i++)
{
if(x>=nums[i])
{
S.insert(SG(x-nums[i])); //插入sg[x-nums[i]]
}
}

for(int i=0;;i++)
{
if(!S.count(i))
{
sg[x]=i;
return sg[x];
}
}
}

int main()
{
memset(sg,-1,sizeof(sg));
cin>>m;
for(int i=0;i<m;i++)
{
cin>>nums[i];
}
cin>>n;
int x;
int out=0;
for(int i=0;i<n;i++)
{
cin>>x;
out^=SG(x);
}
if(out==0)
{
cout<<"No"<<endl;
}else
{
cout<<"Yes"<<endl;
}
return 0;
}