数学基础
数学基础
质数
质数判定
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;
}
}
}
}
- 当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)\) - 当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\)。
证明:
- 异或为0是必败态
- 非0状态一定能转化为0状态
公平组合游戏ICG
若一个游戏满足:
- 由两名玩家交替行动;
- 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
- 不能行动的玩家判负。
则称该游戏为一个公平组合游戏,但城建的棋类游戏,如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子、白子,不符合条件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)。

浙公网安备 33010602011771号