数学基础

数学基础

质数

质数判定

int prime(int x)
{
	if(x <= 1) return 0;
	for(int i = 2; i <= x / i; i++)
		if(x % i == 0) return 0;
	return 1;
}

分解质因数

map<int, int> mp;
for(int i = 2; i <= x / i; i++)
	while(x % i == 0) mp[i] ++, x /= i;
if(x > 1) mp[x] ++;
for(auto i: mp) cout << i.first << ' ' << i.second << endl;

质数筛法

bool st[N];
int prime[N], cnt;
void isprime(int n)
{
	for(int i = 2; i <= n; i++)
	{
		if(!st[i]) prime[cnt++] = i;
		for(int j = 0 ; prime[j] <= n / i; j++)
		{
			st[prime[j] * i] = true;
			if(i % prime[j] == 0) break;
		}
	}
}

约数

试除法求约数

for(int i = 1; i <= x / i; i++)
{
	if(x % i == 0)
	{
		res.push_back(i);
		if(x / i != i) res.push_back(x / i);
	}
}

乘积的约数等于约数的乘积

约数个数

首先分解质因数,然后约数的个数等于每个质数+1相乘

约数之和

首先分解质因数,然后约数的和等于
$(p_{1}^{0} + ... + p_{1} ^ {a_{1}}) \times ... \times (p_{k}^{0} + ... + p_{k} ^ {a_{k}}) $

最大公约数

int gcd(int a, int b)
{
	return !b? a: gcd(b, a%b));
}

整数分块

欧拉函数

欧拉函数的定义

\(1 - N 中与 N 互质的数的个数被称为欧拉函数,记为ϕ(N)。\)
\(由算术基本定理,N分解质因数,那么\)
\(ϕ(N)\) = \(N*\frac{p_1-1}{p_1}*\frac{p_2-1}{p_2}*...*\frac{p_m-1}{p_m}\)\( \)推导:容斥原理$

int phi(int x)
{
	int res = x;
	unordered_map<int, int> mp;
	for(int i = 2 ; i <= x / i; i++)
	{
		while(x % i == 0) x /= i, mp[i] ++;
	}
	if(x > 1) mp[x] ++;
	for(auto i: mp)
	{
		int p = i.first;
		res = res / p * (p - 1);
	}
	return res;
}

筛法求欧拉函数

bool st[N];
int prime[N], el[N], cnt;
void phi(int n)
{
	el[1] = 1;
	for(int i = 2; i <= n; i++)
	{
		if(!st[i]) prime[cnt++] = i, el[i] = i - 1;
		for(int j = 0; prime[j] <= n / i; j++)
		{
			st[prime[j] * i] = true;
			el[prime[j] * i] = el[i] * (prime[j] - 1);
			if(i % prime[j] == 0)
			{
				el[prime[j] * i] = el[i] * prime[j];
				break;
			}
		}
	}
}
  1. 当i % prime[j]
    \(ϕ(i)\) = \(i*\frac{p_1-1}{p_1}*\frac{p_2-1}{p_2}*...*\frac{p_m-1}{p_m}\)
    \(ϕ(i \times p_j)\) = \(i \times p_j*\frac{p_1-1}{p_1}*\frac{p_2-1}{p_2}*...*\frac{p_m-1}{p_m} * \frac{p_j - 1}{p_j}\)
    \(ϕ(i \times p_j) = ϕ(i) \times (p_j - 1)\)
  2. 当i % prime[j] == 0
    \(ϕ(i)\) = \(i*\frac{p_1-1}{p_1}*\frac{p_2-1}{p_2}*...*\frac{p_j-1}{p_j}\)
    \(ϕ(i \times p_j)\) = \(i \times p_j*\frac{p_1-1}{p_1}*\frac{p_2-1}{p_2}*...*\frac{p_m-1}{p_m} * \frac{p_j - 1}{p_j}\)
    \(ϕ(i \times p_j) = ϕ(i) \times p_j\)

快速幂

ll ksm(ll a, ll b, ll p) 
{
    ll ret = 1;
    while(b) 
    {
        if(b&1) ret = ret*a%p;
        a = a*a%p, b >>= 1;
    }
    return ret%p;
}

exgcd

int exgcd(int a, int b, int &x, int &y) //ax+by=gcd(a, b)
{
    if(!b)
    {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a%b, y, x);
    y -= a/b*x;
    return d;
}

线性同余方程

\(a_i * x_i \equiv b_i (mod\ m_i)\)
等价于
\(a_i * x_i + m_i * y_i = b_i\)

    int a, b, m, x, y;
    cin>>a>>b>>m;
    int d = exgcd(a, m, x, y);
    if(b % d) cout << "impossible" << endl;
    else cout << (ll)x * (b / d) % m << endl;

中国剩余定理

高斯消元

模板

int gauss()
{
    int c, r;
    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; //秩<n,看最后n-r行结果是否是0

            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;
}

求异或方程组

#include <bits/stdc++.h>
// 
#define ios ios::sync_with_stdio(false), cin.tie(0);
#define et cout<<endl;
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
typedef unsigned long long ull;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
const int N = 105;
const int M = 2e5+5;
const int mod = 1e9+7;
const double eps = 1e-6;
//
int t, n, k;
int a[N][N];
int ans;
int gauss()
{
	int r, c;
	for(r = c = 0; c < n; c ++)
	{
		int t = r;
		for(int i = r; i < n; i++)
		{
			if(a[i][c])
			{
				t = i;
				break;
			}
		}
		if(!a[t][c]) continue;

		for(int i = c; i <= n; i ++) swap(a[t][i], a[r][i]);

		for(int i = r+1; i < n; i++)
		{
			if(a[i][c])
			{
				for(int j = c; j <= n; j++)
				{
					a[i][j] ^= a[r][j];
				}
			}
		}
		r++;
	}

	if(r < n)
	{
		for(int i = r; i < n; i++)
			if(a[i][n]) 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;
}
void solve()
{
	cin >> n;
	for(int i = 0; i < n; i++)
	{
		for(int j = 0; j <= n; j++)
			cin >> a[i][j];
	}

	int p = gauss();
	if(p == 2)
	{
		cout << "No solution";
	}
	if(p == 1)
	{
		cout << "Multiple sets of solutions";
	}
	if(p == 0)
	{
		for(int i = 0 ; i  < n; i++)
			cout << a[i][n]<< endl;
	}

}
int main()
{
	// ios
	// cin >> t;
	t = 1;
	while(t--)
	{
		solve();
	}
}

组合数

暴力

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

n, m <= 2000 打表

void init()
{
	for(int i = 0; i < N; i++) f[0][i] = 0, f[i][0] = 1;
	for(int i = 1; i < N; i++)
	{
		for(int j = 1; j < N; j++)
			f[i][j] = (f[i-1][j] + f[i-1][j-1]);
	}
}

n, m <= 100000 逆元递推

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) = fact[a] * infact[a-b] % mod * infact[b] % mod;

n, m <= 1e18 Lucas

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

卡特兰数

C(2n, n) - C(2n, n-1)

高精度

容斥原理

博弈论

Nim游戏

给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能取取。取走最后一件物品者获胜。二人均采用最优策略,问先手是否必胜。
我们把这种游戏称为NIM博弈。把游戏中面临的状态称作局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动都会输掉游戏,则称该局面必败。
所谓最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败的局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都知考虑理想情况,即,二人均无失误,都采取最优策略行动时游戏的结果。
NIM博弈不存在平局,只有先手必胜和先手必败两种情况。
NIM定理:NIM博弈先手必胜,当且仅当\(a_{1} \oplus a_{2} \oplus a_{3}... \oplus a_{n} \neq 0\)
证明:

  1. 异或为0是必败态
  2. 非0状态一定能转化为0状态

公平组合游戏ICG

若一个游戏满足:

  1. 由两名玩家交替行动;
  2. 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
  3. 不能行动的玩家判负。
    则称该游戏为一个公平组合游戏,但城建的棋类游戏,如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子、白子,不符合条件2;胜负判定比较复杂,不符合条件3。

有向图游戏

给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,不能移动者判负。该游戏被称为有向图游戏。
任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。

Mex运算

设S表示一个非负数集合。定义Mex(S)为求出不属于集合S的最小非负整数的运算,即
\(Mex(S) = min(P), P = N - S.\)

SG函数

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

有向图游戏的和

设G1, G2, ..., Gm是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1,G2, ... Gm的和。
有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即:
SG(G) = SG(G1) ^ ... SG(Gm)。

定理:有向图游戏的某个局面必胜,当且仅当该局面对应的节点的SG函数值大于等于0。

例题:
给定n堆石子以及一个由k个不同正整数构成的数字集合S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

int sg(int x) //记忆化搜索sg函数
{
	if(f[x] != -1) return f[x];
	unordered_set<int> st;
	for(int i = 1; i <= k; i ++ )
	{
		int sum = s[i];
		if(x >= sum) st.insert(sg(x - sum));
	}
}

int main()
{
	cin >> k;
	for(int i = 1; i <= k; i ++ ) cin >> s[i];
	cin >> n;
	memset(f, -1, sizeof f);
	int ret = 0;
	for(int i = 1; i <= n; i ++ )
	{
		int x; cin >> x;
		ret ^= sg(x);
	}
	if(ret) ...
	else ...
}

例题: 台阶-Nim游戏
现在,有一个n级台阶的楼梯,每级台阶上都有若干个石子,其中第i级台阶上有ai个石子(i≥1)。
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。
已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。

如果把奇数台阶看成一个Nim游戏,当奇数台阶的异或值为0时,此时是必败的。
首先,先手奇数台阶的异或值为0是一个必败态。
这是因为,当奇数台阶的异或值为0时,如果我们移动奇数台阶上的石子,那么对手可以将我们移动的式子再次移动到奇数台阶上,此时奇数台阶的异或值是0,相当于没有变化;
当对手拿到一个异或值非0的局面时,总有办法让局面的异或值变为0。
这样我们拿到手的永远是奇数台阶异或为0的状态,并且最后奇数台阶上的式子会全为0.
此时偶数台阶上仍有石子,但是由于将石子移动到地面要偶数步,我们此时奇数台阶上的石子全是0 了,我们只能移动偶数台阶上的石子,这样全0的局面又留给了自己。

例题: 拆分-Nim游戏
给定n堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
每个游戏G1都会分成若干个子局面,子局面由两个子游戏G11,G12组成。并且,由前面的定理,每个局面可以看成两个有向图子游戏的和,那么每个子局面可以当作是sg(G11) ^ sg(G12)。

posted @ 2021-03-01 18:04  2wx  阅读(114)  评论(0)    收藏  举报