はじめに
競プロ勉強会第2回です。今回は数学的な話を中心にします。
1. 逆元
xを整数、pを素数とすると、xの逆元yは
(証明)a, bを0以上p-1以下とすると、
これ単体で聞かれる事はあまり無いですが、確率をmod pで整数で答えよ、や次のCombinationで用います。
逆元を求める方法はフェルマーの小定理+繰り返し二乗法を用いる方法か拡張ユークリッドの互除法を用いる方法があります。
フェルマーの小定理: 素数pとpと互いに素な整数aに対して
(証明)
同様に
よって
2. Combination
みんな大好きCombinationです。nCkです。
ここで使える有用なテクニックなのですが、
p = (p/n) * n + p%n
(p/n) * n
つまり、n!の逆元とp%(n+1)の逆元が求まれば(n+1)!の逆元が求まるわけです。p%(n+1)はn以下の整数なので、下から順番に記録しておくことで求められます。よって前計算はO(n)です。
<基本公式>
- 最初の1個を取るor取らない
- 念の為
<公式>
の展開
- 基本公式1を使ってΣを展開
- 横k縦nのグリッドグラフの全ての上の部分への行き方の総和
- 横k縦n+1のグリッドグラフの右上への行き方と等しい
- 一番上の縦の辺は必ず1箇所通るから
- 公式3を2回
- 公式3の一般化
- 横k縦n+m-k+1の縦をnとm-kに辺を1行あけて分割
- 横k縦n+m-kを斜めに切る感じ
- 左は下からn, 右は上からmの点を通る直線
3. 素数
素数かどうかの判定はO(
bool is_Prime(int n) {
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
同様に約数列挙とかもできる。素数を前計算で求める方法は、
vector<int> get(int mx) {
vector<int> prime;
for (int i = 2; i <= mx; i++) {
bool ok = true;
for (int j : prime) {
if (i % j == 0) {
ok = false;
break;
}
}
if (ok) {
prime.push_back(mx);
}
}
return prime;
}
但し、これはそこそこ遅い。n<=10^6くらいで素数かどうかを判定するならエラトステネスのふるいを使うとよい。
bool isPrime[1000001]; // trueで初期化
void init(int mx) {
isPrime[0] = isPrime[1] = false;
for (int i = 2; i <= mx; i++) {
if (isPrime[i]) {
for (int j = 2; i * j <= mx; j++) isPrime[i * j] = false;
}
}
}
4. gcd
普通のgcd
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a%b);
}
拡張gcd ax+by=gとなるx, yもついでに求める。
int extgcd(int a, int b, int &x, int &y) {
int d = a;
if (b != 0) {
d = extgcd(b, a%b, y, x);
y -= (a/b) * x;
} else {
x = 1; y = 0;
}
return d;
}
5. 繰り返し二乗法
int pw(int a, int b, int mod) {
if (b == 0) return 1;
if (b % 2 == 1) return pw(a, b-1, mod) * a % mod;
int temp = pw(a, b/2, mod);
return temp * temp % mod;
}
行列とかにも使える。
水色向け練習問題
6. 包除原理
集合の数え上げの手法。ある部分集合がn個の集合に含まれる時、