数学部分学习笔记

数学部分学习笔记

数论

唯一分解定理

定理

对于任意正整数 a,均有:

a=p1k1p2k2pnkn

其中 p1,p2pn均为质数。

约数个数定理

对于任意正整数 a=p1k1p2k2pnkn,其约数个数 k

ans=i=1k(ki+1)

约数和公式

对于任意正整数 a=p1k1p2k2pnkn,其约数和 k

ans=(p10+p11+p1k1)(p20+p21++p2k2)(pn0+pn1++pnkn)

裴蜀定理

采用常用的形式描述:

对于方程 ax+by=c,当且仅当 gcd(a,b)c 时,方程有整数解。

扩展欧几里德算法

  1. 解方程 ax+by=gcd(a,b)

运用以下代码求出一个特解 x0.

void exgcd(int a, int b, int &x, int &y) {
    if (b == 0)
        return x = 1, y = 0, void();
    exgcd(b, a % b, y, x);
    y = y - a / b * x;
}

则方程最小正整数解 x=(x0modb+b)modb

  1. 解方程 ax+by=c

g=gcd(a,b),则:

a. 若 gc,原方程无整数解。

b. 若 gc,先转化为问题1,求方程 ax+by=gcd(a,b) 的一个特解 x0,记 k=bg ,那么原方程的一个特解 x1 就是 x1=k×x0.

方程的最小正整数解就是 x=(x1modk+k)modk.

逆元

a1(modp)

费马小定理求逆元

p 为质数时,我们可以用费马小定理求 a1=ap2.

代码:

int qpow(int x, int y) {
    int ans = 1;
    while (y) {
        if (y & 1)
            ans = ans * x % p;
        x = x * x % p;
        y >>= 1;
    }
    return ans;
}

扩展欧几里德定理求逆元

gcd(a,b)=1时,我们可以通过调用 exgcd(a,p,x,y) 求出 a1=x

代码:

void exgcd(int a, int b, int &x, int &y) {
    if (b == 0)
        return x = 1, y = 0, void();
    exgcd(b, a % b, y, x);
    y -= a / b * x;
}

线性递推求逆元

当需要线性递推求逆元时,采用以下代码求逆元。

inv[1] = 1;
for (int i = 2; i <= n; ++i) {
  inv[i] = (long long)(p - p / i) * inv[p % i] % p;
}

线性同余方程

形如 axb(modn) 的方程称为线性同余方程。

我们将原方程转化为 ax+nk=b,显然当且仅当 gcd(a,n)b 时原方程有解。于是考虑变换方程的形式,记 t=gcd(a,n),先求出 ax+ny=t 时方程的一组解 (x0,y0),即 ax0+by0=t

将原方程两边同 ×bt,得到 abtx0+nbty0=b,于是原方程的解就是 x=x0bt

k=nt,则 x 的最小整数解为 xmin=(xmodk+k)modk

欧拉函数

φ(i) 表示的是小于等于 nn 互质的数的个数。

求单个数欧拉函数的求法:

int solve(int n) {
	int ans = n;
	for (int i = 2; i * i <= n; i++) 
		if (n % i == 0) {
			ans = ans / i * (i - 1);
		while (n % i == 0) 
			n /= i;
    	}
  if (n > 1)
  	ans = ans / n * (n - 1);
  return ans;
}

筛法求欧拉函数:

vector<int>v;
int phi[N];
bitset<N>pri;
int n;
void get_p() {
	phi[1] = 1;
	for (int i = 2; i <= n; i++) {
		if(!pri[i]) {
			v.push_back(i);
			phi[i] = i - 1;
		}
		for(int j = 0; j < (int)v.size() && v[j] * i <= n; j++) {
			pri[i * v[j]] = true;
			if(i % v[j] == 0) {
				phi[i * v[j]] = phi[i] * v[j];
				break;
			}
			phi[i * v[j]] = phi[i] * phi[v[j]];
		}
	}
}

欧拉定理

欧拉定理

gcd(a,m)=1 ,则 aφ(m)1(modm)

扩展欧拉定理

ab{abmodφ(m),gcd(a,m)=1,ab,gcd(a,m)1,b<φ(m),a(bmodφ(m))+φ(m),gcd(a,m)1,bφ(m).(modm)

中国剩余定理

中国剩余定理

中国剩余定可求解如下形式的一元线性同余方程组(其中 n1,n2,,nk 两两互质)

{xa1(modn1)xa2(modn2)xak(modnk)

具体解法:

  1. 计算所有模数的积 n

  2. 对于第 i 个方程:

    a. 计算 mi=nni

    b.计算 mi 在模 ni 意义下的逆元 m1

    c.计算 ci=mimi1(不要对 ni 取模)

  3. 方程组在模 n 意义下的唯一解为 x=i=1kaici(modn)

实现:

int CRT(int m[], int r[]) {
	int M = 1, ans = 0;
	for(int i = 1; i <= n; i++) 
		M *= m[i];
	for(int i = 1; i <= n; i++) {
		int c = M / m[i], x, y;
		exgcd(c, m[i], x, y);
		ans = (ans + r[i] * c * x % M + M) % M;
	}
	return (ans + M) % M;
}

扩展中国剩余定理

当方程组中每一个模数 ni 不互质时,需要用到中国剩余定理。

考虑两个方程的情况。

设两个方程分别是 xa1(modm1)xa2(modm2)

将它们转化为不定方程: x=m1p+a1=m2q+a2 ,其中 p,q 是整数,则有 m1pm2q=a2a1

由 裴蜀定理,当 a2a1 不能被 gcd(m1,m2) 整除时,无解

其他情况下,可以通过 扩展欧几里得算法 解出来一组可行解 (p,q)

则原来的两方程组成的模方程组的解为 xb(modM) ,其中 b=m1p+a1M=lcm(m1,m2)

然后多个方程时两两合并即可。

代码:

int md[N], rem[N];//存模数和余数
int qmul(int a, int b, int p) {//龟速乘
	int ans = 0;
	while (b) {
		if (b & 1)
			ans = (ans + a) % p;
		a = (a + a) % p;
		b >>= 1;
	}
	return ans;
}
int exgcd(int a, int b, int &x, int &y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	int gcd = exgcd(b, a % b, x, y);
	int t = x;
	x = y;
	y = t - a / b * y;
	return gcd;
}
signed main() {
	ios::sync_with_stdio(0);
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> md[i] >> rem[i];
	int X = rem[1];//存解
	int LCM = md[1];//存前 i - 1 个方程模数的lcm
	for(int i = 2; i <= n; i++) {
		int p = md[i];
		int res = ((rem[i] - X) % p + p) % p;
		int x, y, Gcd = exgcd(LCM, p, x, y);
		if(res % Gcd) {//判断无解
			puts("-1");
			return 0;
		}
		x = qmul(x, res / Gcd, p);
		X += x * LCM;
		LCM = p / Gcd * LCM;
		X = (X % LCM + LCM) % LCM;
	}
	cout << X << "\n";
	return 0;
}

排列组合

排列组合三大方法

插板法

不允许为空的插板

n 个元素,分为 k 组,每组保证有一个元素,问方案数。
公式:

(n1k1)

允许为空的插板

n 个元素,分为 k 组,每组允许为空,问方案数。
公式:

(n+k1n)

插空法

若有 nA 类物品和 mB 类物品,将其插入一段长度为 n+m 的序列,且要求 B 类物品不能相邻,求方案数。

公式:

Ann×An+1m

先计算 A 类物品的排列情况:Ann,此时我们认为序列里共有 n+1 个空,此时 B 类物品的情况就是 An+1m 了。最终答案就是二者相乘。

捆绑法

若有 nA 类物品和 mB 类物品,将其插入一段长度为 n+m 的序列,且要求 B 类物品必须相邻,求方案数。

公式:

An+1n+1×Amm

将所有 B 类物品看为一个物品,此时的排列情况是 An+1n+1,再考虑 B 类物品内部的情况,方案数是 Amm,最终答案就是二者相乘。

二项式定理

(a+b)n=i=0n(ni)anibi

二项式反演

形式1

fn 为恰好使用 n 个不同元素形成特定结构的方案数, gn 为从 n 个不同元素中选出 i0 个元素形成特定结构的方案数。

若已知 fngn,则显然:

gn=i=0n(ni)fi

若已知 gnfn,则有:

fn=i=0n(ni)(1)nigi

形式2

fk 为恰好使用 k 个不同元素形成特定结构的方案数, gk 为从 n 个不同元素中选出 ik 个元素形成特定结构的方案数。

若已知 fkgk,则显然:

gk=i=kn(ik)fi

若已知 gkfk,则有:

fk=i=kn(ik)(1)nkgk

或许可以把它理解成一种容斥的变形?

二项式反演的主要用途是在把所求答案是恰好,但至少/不多于这样的答案好求时可以转化答案进行计算。

卢卡斯定理

p 为质数,对于 n,m 很大, p 很小时的公式:

(nm)=(n/pm/p)(nmodpmmodp)

代码:

int C(int n, int m) {
	if(m > n)
		return 0;
	return fac[n] * qpow(fac[m] * fac[n - m] % p, p - 2) % p;
}
int Lucas(int n, int m) {
	if(m > n)
		return 0;
	if(!m)
		return 1;
	return C(n % p, m % p) * Lucas(n / p, m / p) % p;
}

错排问题

错位排列是没有任何元素出现在其有序位置的排列。即,对于 1n 的排列 P ,如果满足 Pii ,则称 Pn 的错位排列。

递推公式:

D1=0, D2=1, D3=2;

Dn=(n1)(Dn1+Dn2)

常用公式

(nm)=(nnm)

(nk)=nk(n1k1)

(nm)=(n1m)+(n1m1)

(n0)+(n1)++(nn)=i=0n(ni)=2n

i=0n(1)i(ni)=[n=0]

i=0m(ni)(mmi)=(m+nm)   (nm)

i=0ni(ni)=n2n1

i=0ni2(ni)=n(n+1)2n2

i=0n(lk)=(n+1k+1)

(nr)(rk)=(nk)(nkrk)

i=0n(nii)=Fn+1

其中 F 是斐波那契数列。

i=0ni2(ni)=n(n+1)2n2

数学方法

高斯消元

一般使用高斯-约旦消元法

// Gauss_Jordan 模板
#include <bits/stdc++.h>
#define N 105
using namespace std;
const double eps = 1e-6;
int n;
double a[N][N];
bool gauss_jordan() {
    for (int i = 1; i <= n; i++) {
        int r = i;
        for (int k = i; k <= n; k++)
            if (fabs(a[k][i]) > eps) {
                r = k;
                break;
            }
        if (r != i)
            swap(a[r], a[i]);
        if (fabs(a[i][i]) < eps)
            return false;
        for (int k = 1; k <= n; k++) {
            if (k == i)
                continue;
            double t = a[k][i] / a[i][i];
            for (int j = i; j <= n + 1; j++)
                a[k][j] -= t * a[i][j];
        }
    }
    for (int i = 1; i <= n; i++)
        a[i][n + 1] /= a[i][i];
    return true;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n + 1; j++)
            scanf("%lf", &a[i][j]);
    if (gauss_jordan())
        for (int i = 1; i <= n; i++)
            printf("%.2lf\n", a[i][n + 1]);
    else
        puts("No Solution");
    return 0;
}

BSGS

给定一个质数 p,以及一个整数 a,一个整数 b,现在要求你计算一个最小的非负整数 l,满足 alb(modp)

#include <bits/stdc++.h>
#define int long long
using namespace std;
int p, a, b;
int BSGS() {
	unordered_map<int, int>mp;
	a %= p;
	b %= p;
	int s = ceil(sqrt(p));
	int t = b;
	mp[b] = 0;
	for (int i = 1; i <= s; i++) {
		t = t * a % p;
		mp[t] = i; 
	}
	int x = 1;
	for (int i = 1; i <= s; i++)
		x = x * a % p;
	t = 1;
	for (int i = 1; i <= s; i++) {
		t = t * x % p;
		if (mp.count(t))
			return i * s - mp[t];
	}
	return -1;
}
signed main() {
	cin >> p >> a >> b;
	int tmp = BSGS();
	if(~tmp)
		cout << tmp << "\n";
	else
		puts("no solution");
	return 0;
}
posted @   长安19路  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示