二分图&数学
一、二分图
定义:二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集( i ∈ A i \in A i∈A , j ∈ B j \in B j∈B ),则称图G为一个二分图。不存在奇数环的无向图。
二分图判定:染色法
实现方式:以任意一点为起点进行染色,dfs遍历所有点,相邻的点染不同颜色,相隔一个点的两染相同颜色,边遍历边判定是否有冲突,(即判定相邻的两点是否为同一颜色,)如果有冲突则不存在二分图。
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int h[N], e[N], ne[N], idx;
int color[N];
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
bool dfs(int u, int c) {
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i]) {
if (!color[e[i]]) {
if (!dfs(e[i], 3 - c)) return false;
}
else if (color[e[i]] == c) return false;
}
return true;
}
int main() {
memset(h, -1, sizeof(h));
scanf("%d %d", &n, &m);
for (int i = 0; i < m; i++) {
int a, b;
scanf("%d %d", &a, &b);
add(a, b); add(b, a);
}
for (int i = 1; i <= n; i++) {
if (!color[i]) {
if (!dfs(i, 1)) {
printf("No\n");
return 0;
}
}
}
printf("Yes\n");
return 0;
}
二、二分图的最大匹配
匹配方法:匈牙利算法(月老牵线)
实现方式:枚举一边(男生)去匹配另一边(找女生),如果可以匹配成功则进行标记(牵红线),如果不成功,找到那个没能成功匹配的那个点已经匹配的点(找到那个女生的现任男朋友),查找那个点有没有其他可以匹配的点可供替换(找那个现任男朋友的备胎),有则有希望,可以尝试让那个点重新匹配(挖那个现任男朋友的墙角),使其重新匹配一个点(让他换一个女朋友)。
代码实现:
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e5 + 10;
int n1, n2, m;
int h[N], e[N], ne[N], idx;
int match[N];
bool st[N];
int ans;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool find(int x) {
for (int i = h[x]; i != -1; i = ne[i]) {
if (!st[e[i]]) {
st[e[i]] = true;
if (match[e[i]] == 0 || find(match[e[i]])) {
match[e[i]] = x;
return true;
}
}
}
return false;
}
int main() {
scanf("%d %d %d", &n1, &n2, &m);
memset(h, -1, sizeof(h));
for (int i = 0; i < m; i++) {
int a, b;
scanf("%d %d", &a, &b);
add(a, b);
}
for (int i = 1; i <= n1; i++) {
memset(st, false, sizeof(st));
if (find(i)) ans++;
}
printf("%d\n", ans);
return 0;
}
三、欧拉函数 Φ \Phi Φ
ϕ ( n ) \phi(n) ϕ(n):表示 1 − n 1-n 1−n 中与 n n n 互质的数的个数
1.公式:
N = p 1 α 1 ∗ p 2 α 2 ∗ p 3 α 3 ∗ . . . ∗ p k α k N = p_1^{\alpha_1} * p_2^{\alpha_2} * p_3^{\alpha_3} *...* p_k^{\alpha_k} N=p1α1∗p2α2∗p3α3∗...∗pkαk
ϕ ( N ) = N ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ ( 1 − 1 p 3 ) ∗ . . . ∗ ( 1 − 1 p k ) \phi(N) = N * (1 - \cfrac{1}{p_1}) * (1 - \cfrac{1}{p_2}) * (1 - \cfrac{1}{p_3}) *...* (1 - \cfrac{1}{p_k}) ϕ(N)=N∗(1−p11)∗(1−p21)∗(1−p31)∗...∗(1−pk1)
2.证明:(容斥原理)
1.从
1
−
n
1-n
1−n 中去掉所有
p
1
,
p
2
,
p
3
,
.
.
.
,
p
k
p_1, p _2, p_3,..., p_k
p1,p2,p3,...,pk 的倍数;
2.加上所有
p
i
∗
p
j
p_i * p_j
pi∗pj 的倍数数;
3.去掉所以
p
i
∗
p
j
∗
p
k
p_i * p_j * p_k
pi∗pj∗pk 的倍数;
3.加上所有
p
i
∗
p
j
∗
p
k
∗
p
l
p_i * p_j * p_k * p_l
pi∗pj∗pk∗pl 的倍数
.
.
.
n…
所以:
N
N
N
−
N
p
1
−
N
p
2
−
N
p
3
−
.
.
.
−
N
p
k
- \cfrac{N}{p_1} - \cfrac{N}{p_2} - \cfrac{N}{p_3} -...- \cfrac{N}{p_k}
−p1N−p2N−p3N−...−pkN
+
N
p
1
∗
p
2
+
N
p
2
∗
p
3
+
N
p
3
∗
p
4
+
.
.
.
+
N
p
k
−
1
∗
p
k
+ \cfrac{N}{p_1 * p_2} + \cfrac{N}{p_2 * p_3} + \cfrac{N}{p_3 * p_4} +...+ \cfrac{N}{p_{k-1} * p_k}
+p1∗p2N+p2∗p3N+p3∗p4N+...+pk−1∗pkN
−
.
.
.
-...
−...
+
(
−
1
)
k
∗
N
p
1
∗
p
2
∗
p
3
∗
.
.
.
∗
p
k
+ (-1)^k * \cfrac{N}{p_1 * p_2 * p_3 *...* p_k}
+(−1)k∗p1∗p2∗p3∗...∗pkN
= N ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ ( 1 − 1 p 3 ) ∗ . . . ∗ ( 1 − 1 p k ) = N * (1 - \cfrac{1}{p_1}) * (1 - \cfrac{1}{p_2}) * (1 - \cfrac{1}{p_3}) *...* (1 - \cfrac{1}{p_k}) =N∗(1−p11)∗(1−p21)∗(1−p31)∗...∗(1−pk1)
代码实现:
#include <iostream>
#include <cstdio>
using namespace std;
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
int a, ans;
scanf("%d", &a);
ans = a;
for (int i = 2; i <= a / i; i++) {
if (a % i == 0) {
ans = ans / i * (i - 1);
while (a % i == 0) a /= i;
}
}
if (a > 1) ans = ans / a * (a - 1);
printf("%d\n", ans);
}
return 0;
}
3.筛法求欧拉函数
原理:
因为:
p
r
i
m
e
s
<
=
n
/
i
primes <= n / i
primes<=n/i
然后:
情况 1: i % primes == 0
有: ϕ ( p r i m e s ∗ i ) = p r i m e s ∗ ϕ ( i ) \phi(primes * i) = primes * \phi(i) ϕ(primes∗i)=primes∗ϕ(i)
情况 2: i % primes != 0
有 : ϕ ( p r i m e s ∗ i ) = ( p r i m e s − 1 ) ∗ ϕ ( i ) \phi(primes * i) = (primes - 1)* \phi(i) ϕ(primes∗i)=(primes−1)∗ϕ(i)
推导:联系“公式”易证
代码实现:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e6 + 10;
int n;
int primes[N], cnt;
int phi[N];
bool st[N];
long long get_eulers(int x) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[++cnt] = i;
phi[i] = i - 1;
}
for (int j = 1; primes[j] <= x / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) {
phi[primes[j] * i] = primes[j] * phi[i];
break;
}
phi[primes[j] * i] = (primes[j] - 1) * phi[i];
}
}
long long ans = 0;
for (int i = 1; i <= x; i++)
ans += phi[i];
return ans;
}
int main() {
scanf("%d", &n);
printf("%lld\n", get_eulers(n));
return 0;
}
四、欧拉定理
若 a a a 与 n n n 互质,则有: a ϕ ( n ) ≡ 1 a^{\phi(n)} \equiv 1 aϕ(n)≡1 ( m o d (mod (mod n ) n) n)
证明:
假设:
1
−
n
1 - n
1−n 中, 与
n
n
n 互质的数有:
a
1
,
a
2
,
a
3
,
.
.
.
,
a
ϕ
(
n
)
a_1, a_2, a_3,..., a_{\phi(n)}
a1,a2,a3,...,aϕ(n)。①
由于:
g
c
d
(
a
,
n
)
=
=
1
gcd(a, n) == 1
gcd(a,n)==1
所以:
a
∗
a
1
,
a
∗
a
2
,
a
∗
a
3
,
.
.
.
,
a
∗
a
ϕ
(
n
)
a * a_1, a * a_2, a * a_3,..., a * a_{\phi(n)}
a∗a1,a∗a2,a∗a3,...,a∗aϕ(n) 也与
n
n
n 互质。②
由于
1
−
n
1 - n
1−n 中, 与
n
n
n 互质的数有只有
ϕ
(
n
)
\phi(n)
ϕ(n) 个,所以 ①,②两组数在
m
o
d
mod
mod
n
n
n 的情况下是同一组数。
所以有:
a
1
∗
a
2
∗
a
3
∗
.
.
.
∗
a
ϕ
(
n
)
≡
a
ϕ
(
n
)
∗
(
a
1
∗
a
2
∗
a
3
∗
.
.
.
∗
a
ϕ
(
n
)
)
a_1 * a_2 * a_3 *...* a_{\phi(n)} \equiv a^{\phi(n)} * (a_1 * a_2 * a_3 *...* a_{\phi(n)})
a1∗a2∗a3∗...∗aϕ(n)≡aϕ(n)∗(a1∗a2∗a3∗...∗aϕ(n))
(
m
o
d
(mod
(mod
n
)
n)
n)
两边消去 a 1 ∗ a 2 ∗ a 3 ∗ . . . ∗ a ϕ ( n ) a_1 * a_2 * a_3 *...* a_{\phi(n)} a1∗a2∗a3∗...∗aϕ(n),
得: a ϕ ( n ) ≡ 1 a_{\phi(n)} \equiv 1 aϕ(n)≡1 ( m o d (mod (mod n ) n) n)
证毕。
费马定理: a n − 1 ≡ 1 a^{n - 1} \equiv 1 an−1≡1 ( m o d (mod (mod n ) n) n)
五、快速幂:
求: a b a^b ab m o d mod mod p p p
long long quick_mi(long long a, long long b, long long p) {
long long ans = 1, tmp = a;
while (b) {
if (b & 1) ans = ans * tmp % p;
tmp = tmp * tmp % p;
b >>= 1;
}
return ans;
}
快速幂求逆元
逆元:由于 a b \cfrac{a}{b} ba m o d mod mod p p p ≠ \neq = a m o d p b m o d p \cfrac{amodp}{bmodp} bmodpamodp ,对于大整数 a a a, b b b, 不能处理,所以需要有一个数x,使得 a b \cfrac{a}{b} ba m o d mod mod p p p = = = a ∗ x a * x a∗x m o d mod mod p p p。其中 x x x 即为 b b b 的逆元。
求逆元最常用的方法有两种:1.快速幂求逆元;2.扩展欧几里得算法求逆元。这里是快速幂求逆元,下面会有用扩展欧几里得算法求逆元。
推导:
a b \cfrac{a}{b} ba ≡ \equiv ≡ a ∗ x a * x a∗x ( m o d (mod (mod p ) p) p)
1 b \cfrac{1}{b} b1 ≡ \equiv ≡ x x x ( m o d (mod (mod p ) p) p)
b ∗ x b * x b∗x ≡ \equiv ≡ 1 1 1 ( m o d (mod (mod p ) p) p)
由于 p p p 为质数, 所以有 b p − 1 ≡ 1 b^{p - 1} \equiv 1 bp−1≡1 ( m o d (mod (mod p ) p) p),
所以, x = b p − 2 x = b^{p-2} x=bp−2
#include <cstdio>
#include <iostream>
using namespace std;
int t;
long long a, p;
long long quick_mi(long long a, long long p) {
long long ans = 1, tmp = a, res = p - 2;
while (res) {
if (res & 1) ans = ans * tmp % p;
tmp = tmp * tmp % p;
res >>= 1;
}
return ans;
}
int main() {
scanf("%d", &t);
for (int i = 0; i < t; i++) {
scanf("%lld %lld", &a, &p);
if (a % p == 0) printf("impossible\n");
else printf("%lld\n", quick_mi(a, p));
}
return 0;
}
六、扩展欧几里得算法
裴蜀定理:对于一对任意的正整数 a , b a, b a,b, 一定存在整数 x , y x, y x,y, 使得 a ∗ x + b ∗ y = ( a , b ) a * x + b * y = (a, b) a∗x+b∗y=(a,b)。
代码实现:
void exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1, y = 0;
return;
}
exgcd(b, a % b, y, x);
y -= a / b * x;
}
a ∗ x + b ∗ y = ( a , b ) a * x + b * y = (a, b) a∗x+b∗y=(a,b)
a a a % b ∗ x + b ∗ y = ( a , b ) b * x + b * y = (a, b) b∗x+b∗y=(a,b)
因为: a a a % b b b = = = a − a / b ∗ b a - a / b * b a−a/b∗b
所以: a ∗ x + b ∗ ( y − a / b ∗ x ) = ( a , b ) a * x + b * (y - a / b * x) = (a, b) a∗x+b∗(y−a/b∗x)=(a,b)
所以一轮递归需要处理的只有 y = y − a / b ∗ x y = y - a / b * x y=y−a/b∗x
扩展欧几里得算法求逆元:
#include <iostream>
#include <cstdio>
using namespace std;
void exgcd(long long a, long long b, long long &x, long long &y) {
if (!b) {
x = 1, y = 0;
return;
}
exgcd(b, a % b, y, x);
y -= a / b * x;
}
int main() {
int t;
scanf("%d", &t);
for (int i = 0; i < t; i++) {
long long a, p, x, y;
scanf("%lld %lld", &a, &p);
if (a % p == 0) printf("impossible\n");
else
{
exgcd(a, p, x, y);
printf("%lld\n", (x % p + p) % p);
}
}
}