数论基础

数论

质数

在大于1的整数中,如果只包含1和本身这两个约数,就被称为约数,或者叫素数。
质数定理: 1~n中有 n/ln(n) 个质数

1. 质数的判定-试除法

bool is_prime(int x)
{
    if (x < 2) return false;
    for (int i = 2;i <= x / i;i++)
        if (x % i == 0)
            return false;
    return true;
}

2. 分解质因数-试除法

n中最多只包含一个大于sqrt(n)的质因子
void divide(int x)
{
    for(int i = 2; i <= x / i; i++)
        if(x % i == 0) // i 一定是质数
        {
            int s = 0;
            while(x % i == 0)
            {
                x /= i;
                s++;
            }
            cout << i << s << endl;
        }
    if(x > 1) cout << x << 1 << endl;
}

3. 筛质数

朴素筛法

时间复杂度: n/2 + n/3 + ... + n/n = n(1/2 + 1/3 + ... + 1/n) 
调和级数ln(n) < log2(n) 因此时间复杂度可表示为O(nlgn)
void get_primes(int n)
{
    for(int i = 2; i <= n; i++)
    {
        if(!st[i])
        {
            primes[cnt++] = n;
        }
        for(int j = i + i; j <= n; j += i)
            st[j] = 1;
    }
}

埃氏筛法

优化: 只需筛掉质数的所有倍数 O(nloglogn)

void get_primes(int n)
{
    for(int i = 2; i <= n; i++)
    {
        if(!st[i])
        {
            primes[cnt++] = n;
            for(int j = i + i; j <= n; j += i)
                st[j] = 1;
        }
        
    }
}

线性筛法

数据范围1e7时,比埃氏筛法快1倍

void get_primes(int n)
{
    for(int i = 2; i <= n; i++)
    {
        if (!st[i]) primes[cnt++] = i;
        for (int j = 0;primes[j] <= n / i;j++)
        {
            st[primes[j] * i] = 1;
            if (i % primes[j] == 0) break; // primes[j]一定是i的最小质因子
        }
    }
}

约数

试除法求约数

set<int> get_divisors(int n)
{
    set<int> res;
    for (int i = 1; i <= n / i; i++) // 枚举到sqrt(n) 只筛小的约数
    {
        if (n % i == 0)
        {
            res.insert(i);
            if (i != n / i) res.insert(n / i);
        }
    }
    return res;
}

约数个数

分解质因数之后
公式:N=(α1+1)(α2+1)(α3+1)…(αk+1)
unordered_map<int, int> primes;
int n;
cin >> n;
while (n--)
{
    int x;
    cin >> x;
    for (int i = 2;i <= x / i;i++)
    {
        while (x % i == 0)
        {
            x /= i;
            primes[i]++;
        }
    }
    if(x > 1) primes[x]++;
}
LL  res = 1;
for (auto item : primes) res = res * (1 + item.second) % mod ;
cout << res << endl;

约数之和

公式:Sum=(1+p1+p1^2+…+p1^α1)(1+p2+p2^2+…+p2^α2)…(1+pk+pk^2+…+p1^αk)
// t = 1 while(a--) t = t * p + 1
for (auto item : primes) 
{
    int p = item.first, a = item.second;
    LL t = 1;
    while (a--)
    {
        t = (t * p + 1) % mod;
    }
    res = res * t % mod;
}

欧几里得算法(辗转相除法)

d|a d|b 那么 d|(ax+by)
(a, b) = (b, a mod b)
//__gcd(a,b) #include <algorithm>
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

欧拉函数

定义:1~n中与n互质的数的个数
分解质因数 N = p1^α1*p2^α2...pk^αk
φ(N) = N(1 - 1/p1)(1 - 1/p2)...(1 - 1/pk)

公式法

int x;
cin >> x;
int res = x;
for (int i = 2;i <= x / i;i++)
{
    if (x % i == 0)
    {
        res = res / i * (i - 1);
        while (x % i == 0)
            x /= i;
    }
}
if(x > 1) res = res / x * (x - 1);
cout << res << endl;

筛法求欧拉函数

线性筛法 
i mod pj == 0 :
    φ(pj * i) = pj * φ(i) 因为pj是i的最小质因子
i mod pj != 0 : 
    φ(pj * i) = pj * φ(i) * (1 - 1/pj) = φ(i) * (pj - 1)
    pj 是pj*i的最小质因子且pj不在i的质因子当中的
//求1~n欧拉函数之和
LL get_eulers(int n)
{
    phi[1] = 1;
    for (int i = 2; i <= n;i++)
    {
        if (!st[i])
        {
            primes[cnt++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0;primes[j] <= n / i;j++)
        {
            st[primes[j] * i] = 1;
            if (i % primes[j] == 0)
            {
                phi[primes[j] * i] = phi[i] * primes[j];
                break;
            }
            //e[pj*i] = e[i]*pj*(1-1/pj) = e[i]*
            phi[primes[j] * i] = phi[i] * (primes[j] - 1);
        }
    }
    LL res = 0;
    for (int i = 1;i <= n;i++) res += phi[i];
    return res;
}

欧拉定理

a和n互质 则a^φ(n) 同余 1 (mod n)
a^φ(n) mod n = 1 
费马定理 a^(p-1) % (p-1) = 1

快速幂

O(logk) 求 a^k mod p
LL qmi(int a, int k, int p)
{
    LL res = 1;
    while (k)
    {
        if (k & 1) res = res * (LL)a % p;
        k >>= 1;
        a = (LL)a * a % p;
    }
    return res;
}

快速幂求逆元

规定p为质数
1. a p互质
    由费马定理 a^(p-1) = 1 (mod p) 由 a * a^(p-2) = 1 (mod p)
    res = qmi(a, p - 2, p);
2. 不存在

扩展欧几里得算法

裴蜀定理:对于任意正整数a,b,一定存在非零整数x,y,使得ax+by = (a,b)
(a, b) = (b, a % b)
1. (a, 0) = a  x = 1 y = 0
2. return (b, a % b) = d = by + (a mod b)x = by + (a - [a/b]*b)x
    整理得到 ax + b(y - [a/b]*x) = d
    故 x = x y = y - [a/b]*x
int exgcd(int a, int b, int &x, int &y)
{
    if (!b)
    {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y = y - a / b * x;
    return d;
}

线性同余

$a_i × x_i ≡ b_i (mod\ m_i)$

化简为 $ax + my = b$

由扩展欧几里得 $ax + my = d = (a,b)$

因此若b为d的倍数,则$x = x × (b/d) % m$

若m不为质数 只能使用该方法求逆元 快速幂求逆元要求m为质数

中国剩余定理

x = a1 * M1 * M1-1 + ...

高斯消元

在$n^3$时间复杂度求出非齐次线性方程组 最简阶梯型矩阵

有3种情况

1. 无解 0 != 0
2. 无穷多解 出现 0 == 0
3. 唯一解 满秩

0表示唯一解 1表示无穷解 2表示无解

#include <iostream>
#include <cmath>

using namespace std;

int const N = 110;
const double eps = 1e-8;

int n;
double a[N][N];

int gauss()
{
    int c, r; // column表示列 row表示行
    for (c = 0, r = 0; c < n; c++)
    {
        int t = r;
        for (int i = r; i < n; i++)
            if (fabs(a[i][c]) > fabs(a[t][c])) // 找到次列绝对值最大的行
                t = i;
        if (fabs(a[t][c]) < eps) continue; // 全为0不用消啦

        for (int i = c; i <= n; i++) swap(a[t][i], a[r][i]);  // 将绝对值最大的行换到最顶端
        for (int i = n; i >= c; i--) a[r][i] /= a[r][c]; // 将当前行的首位变成1
        for (int i = r + 1; i < n; i++)
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j--)
                    a[i][j] -= a[r][j] * a[i][c];
        r++;
    }

    if (r < n)
    {
        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][j] * a[j][n];
    
    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 = gauss();
    if (t == 0)
    {
        for (int i = 0; i < n; i++)
        {
            if(fabs(a[i][n]) < eps) a[i][n] = 0;
            printf("%.2f\n", a[i][n]);
        }
    }
    else if (t == 1)
        puts("Infinite group solutions");
    else
        puts("No solution");
    return 0;
}

组合计数

递推 1e3*1e3

//c[i][j] = c[i-1][j] + c[i - 1][j - 1]
void init()
{
    for(int i = 0; i < N; i ++)
        for(int j = 0; j <= i; j ++)
            if(!j) c[i][j] = 1;
            else c[i][j] = (c[i-1][j] + c[i - 1][j - 1]) % mod; 
}

递推 1e5 * 1e5

void init()
{
    fact[0] = infact[0] = 1;
    for(int i = 1; i < N; i++)
    {
        fact[i] = (LL)fact[i - 1] * i % mod;
        infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
    }
}
//C(a,b) == (LL)fact[a] * infact[b] % mod * infact[a - b] % mod 
// a! / b!(a - b)!

lucas

C(a,b) 同余 C(a%p,b%p) * C(a/p,b/p) (mod p)

#include <iostream>

using namespace std;

typedef long long LL;

int qmi(int a, int k, int p)
{
    int res = 1;
    while (k)
    {
        if(k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

int C(int a, int b, int p)
{
    if(b > a) return 0;
    int res = 1;
    for(int i = a, j = 1; j <= b; i--, j ++)
    {
        res = (LL)res * i % p;
        res = (LL) res * qmi(j, p - 2, p) % p;
    }
    return res;
}

int lucas(LL a, LL b, int p)
{
    if(a < p && b < p) return C(a, b, p);
    return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}

int main()
{
    int n;
    cin >> n;
    while (n--)
    {
        LL a, b;
        int p;
        cin >> a >> b >> p;
        cout << lucas(a, b, p) << endl;
    }
}

高精度

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int const N = 5010;
int primes[N], cnt, st[N];
int sum[N];

void get_primes(int n)
{
    for (int i = 2; i <= n;i++)
    {
        if (!st[i]) primes[cnt++] = i;
        for (int j = 0;primes[j] <= n / i;j++)
        {
            st[primes[j] * i] = 1;
            if (i % primes[j] == 0) break;
        }
    }
}

int get(int n, int p)
{
    int res = 0;
    while (n)
    {
        res += n / p;
        n /= p;
    }
    return res;
}


vector<int> mul(vector<int> a, int b)
{
    int t = 0;
    vector<int> c;
    for (int i = 0;i < a.size();i++)
    {
        t += b * a[i];
        c.push_back(t % 10);
        t /= 10;
    }
    while (t)
    {
        c.push_back(t % 10);
        t /= 10;
    }
    return c;
}

int main()
{
    int a, b;
    cin >> a >> b;
    get_primes(a);
    for (int i = 0;i < cnt;i++)
    {
        int p = primes[i];
        sum[i] = get(a, p) - get(b, p) - get(a - b, p);
    }

    vector<int> res;
    res.push_back(1);
    for (int i = 0;i < cnt;i++)
        for (int j = 0;j < sum[i];j++)
            res = mul(res, primes[i]);

    for (int i = res.size() - 1;i >= 0;i--) cout << res[i];

    return 0;
}

卡特兰数

火车进站 括号序列 01串
C(2n,n) - C(2n,n-1) = C(2n,n)/(n+1)
int n;
cin >> n;
int a = 2 * n, b = n;
int res = 1;
for (int i = a, j = 1; j <= b; i--, j++)
{
    res = (LL)res * i % mod;
    res = (LL)res * qmi(j, mod - 2, mod) % mod;
}
res = (LL)res * qmi(n + 1, mod - 2, mod) % mod;

容斥原理

C(n,0) + C(n,1) + .. + C(n,n) = 2^n
可以看成 求n个物品的所有组合方案
这就是容斥原理的时间复杂度
//能被整除的数
#include <iostream>

using namespace std;

typedef long long LL;
int const N = 20;
int p[N];

int main()
{
	int n, m;
	int res = 0;
	cin >> n >> m;
	for (int i = 0; i < m; i++) cin >> p[i];
	for (int i = 1; i < 1 << m; i++)
	{
		int cnt = 0, t = 1;
		for (int j = 0; j < m; j++)
			if (i >> j & 1)
			{
				if ((LL)t * p[j] > n)
				{
					t = -1;
					break;
				}
				t *= p[j];
				cnt++;
			}
		if (t != -1)
        {
            if (cnt % 2) res += n / t;
            else res -= n / t;
        }
	}
	cout << res << endl;
	return 0;
}

简单博弈论

先手必胜态:可以走到某一个必败状态
先手必败态:走不到任何一个必败状态

Nim游戏

$a_1⊕a_2⊕ ... ⊕a_n = x$
if(x) 先手必胜
else 后手必胜

台阶Nim游戏

只需处理奇数台阶上的数

SG函数

在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1,y2,····yk,定义SG(x)的后记节点y1,y2,····
yk的SG函数值构成的集合在执行mex运算的结果,即:
SG(x)=mex({SG(y1),SG(y2)····SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即 SG(G)=SG(s).

集合Nim游戏

#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;

int n, m, res;
int const N = 1000010;
int s[N], f[N];

int sg(int x)
{
	if (f[x] != -1) return f[x];
	unordered_set<int> S;
	for (int i = 0; i < m; i++)
	{
		if (s[i] > x) continue;
		S.insert(sg(x - s[i]));
	}

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

int main()
{
	cin >> m;
	for (int i = 0; i < m; i++) cin >> s[i];
	cin >> n;
	memset(f, -1, sizeof f);
	for (int i = 0; i < n; i++)
	{
		int x;
		cin >> x;
		res ^= sg(x);
	}
	if (res) puts("Yes");
	else puts("No");
}

拆分Nim

#include <cstring>
#include <iostream>
#include <unordered_set>

using namespace std;

int const N = 110;
int f[N];

int sg(int x)
{
	if (f[x] != -1) return f[x];

	unordered_set<int> S;
	for (int i = 0; i < x; i++)
		for (int j = 0; j <= i; j++)
			S.insert(sg(i) ^ sg(j));

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

int main()
{
	int n;
	cin >> n;
	int res = 0;
	memset(f, -1, sizeof f);
	for (int i = 0; i < n; i++)
	{
		int x;
		cin >> x;
		res ^= sg(x);
	}
	if (res) puts("Yes");
	else puts("No");
}
相邻两个自然数互质
//异或前缀和
s[i] = s[i-1] ^ a[i];
//l ~ r
s[r] ^ s[l - 1]
//已知正方形两点坐标 求另外两点
int x3 = x2 - (y2 - y1);
int y3 = y2 - (x1 - x2);
int x4 = x1 - (y2 - y1);
int y4 = y1 - (x1 - x2);

分拆数

k部分分拆数

#include <iostream>

using namespace std;

typedef long long LL;

LL f[2500][30]; // f[i][j]表示将i拆分成j个数相加的方案数
// 集合划分 1. 包含1 f[i - 1][j - 1]
//         2. 所有数都大于1 f[i - j][j]

int main()
{
    int n = 2022, k = 10;
    f[0][0] = 1;
    for (int i = 1;i <= n;i++)
        for (int j = 1;j <= k;j++)
            if (i >= j)
                f[i][j] = f[i - 1][j - 1] + f[i - j][j];
    cout << f[2022][10];
}

k部分互异分拆数

#include <iostream>

using namespace std;

typedef long long LL;

LL f[2500][30];
// 集合划分 有1 f[i - k][k - 1]
//         无1 f[i - k][k]

int main()
{
    int n = 2022, k = 10;
    f[0][0] = 1;
    for (int i = 1;i <= n;i++)
        for (int j = 1;j <= k;j++)
            if (i >= j)
                f[i][j] = f[i - j][j - 1] + f[i - j][j];
    cout << f[2022][10]; 
// 379187662194355221
}

posted @ 2023-10-13 17:21  Jannan  阅读(47)  评论(0)    收藏  举报