题面
link 给定一个完全二分图,图的左右两边的顶点数目相同,都是
n
n
n。我们要给图中的每条边染成红色、蓝色、或者绿色,并使得任意两条红边不共享端点、同时任意两条蓝边也不共享端点。
计算所有满足条件的染色的方案数,并对
1
0
9
+
7
10^9+7
109+7 取模,其中
n
≤
1
e
7
n ≤ 1e7
n≤1e7。
分析
完全二分图是指左边的每一个点与右边的每一个点都有连线。其实这题涂成绿色可以看成不涂,我们只需要考虑涂成红色和蓝色的情况。
经过简单思考,我们发现只涂一种颜色是比较简单的,若是一边顶点数是
n
n
n,则只涂一种颜色的方案数为
∑
i
=
0
n
C
n
i
A
n
i
\sum_{i=0}^{n}C_n^i A_n^i
∑i=0nCniAni (左边每个顶点最多只有一条出边染色,若有染色边的顶点数为
i
i
i, 有
C
n
i
C_n^i
Cni 种选法,由于要有顺序地映射到右边顶点,则有
A
n
i
A_n^i
Ani 种映射),我们可以记这个值为
F
n
F_n
Fn。
而两种颜色就比较复杂了,若是两种颜色选取的方式是独立的,那么答案就是
F
n
2
F_n^2
Fn2,但实际上左边的一个点与右边的一个点仅有一条连线,不能既涂上红色又涂成蓝色,所以
F
n
2
F_n^2
Fn2 是算多了,所以我们这时候考虑虑容斥原理来去重。
若记有红蓝色涂边重复为事件
P
P
P,具体一些
P
i
j
P_{ij}
Pij表示左边第
i
i
i 个点 和右边第
j
j
j 个点选边重复,我们所需要的答案
a
n
s
=
F
n
2
−
∣
P
∣
ans = F_n^2 - |P|
ans=Fn2−∣P∣, 而根据容斥原理:
P
=
∣
P
11
∪
P
12
.
.
.
∪
P
1
n
.
.
.
∪
P
n
n
∣
=
∑
∣
P
i
j
∣
−
∑
∣
P
i
1
j
1
∩
P
i
2
j
2
∣
.
.
.
+
(
−
1
)
k
+
1
∑
∣
P
i
1
j
1
∩
P
i
2
j
2
.
.
.
∩
P
i
k
j
k
∣
+
.
.
.
P = |P_{11} \cup P_{12}... \cup P_{1n}...\cup P_{nn}| = \sum|P_{ij}| - \sum|P_{i_1j_1} \cap P_{i_2j_2}| ... + (-1)^{k+1} \sum|P_{i_1j_1} \cap P_{i_2j_2}...\cap P_{i_kj_k}| + ...
P=∣P11∪P12...∪P1n...∪Pnn∣=∑∣Pij∣−∑∣Pi1j1∩Pi2j2∣...+(−1)k+1∑∣Pi1j1∩Pi2j2...∩Pikjk∣+...
说的通俗一些,红蓝色涂边重复的方案 = 至少有一条重复的方案 = 组合枚举某一条边重复的方案 - 组合枚举某两条边重复的方案 …=
C
n
1
A
n
1
F
n
−
1
2
−
C
n
2
A
n
2
F
n
−
2
2
.
.
.
+
(
−
1
)
n
+
1
C
n
2
A
n
2
F
0
2
=
∑
i
=
1
n
(
−
1
)
i
+
1
C
n
i
A
n
i
F
n
−
i
2
C_n^1 A_n^1 F_{n-1}^2 - C_n^2 A_n^2 F_{n-2}^2... + (-1)^{n+1}C_n^2 A_n^2 F_{0}^2 = \sum_{i=1}^n (-1)^{i+1}C_n^i A_n^i F_{n-i}^2
Cn1An1Fn−12−Cn2An2Fn−22...+(−1)n+1Cn2An2F02=∑i=1n(−1)i+1CniAniFn−i2, 于是可以得到:
a
n
s
=
F
n
2
−
∣
P
∣
=
F
n
2
−
∑
i
=
1
n
(
−
1
)
i
+
1
C
n
i
A
n
i
F
n
−
i
2
=
C
n
0
A
n
0
F
n
2
+
∑
i
=
1
n
(
−
1
)
i
C
n
i
A
n
i
F
n
−
i
2
=
∑
i
=
0
n
(
−
1
)
i
C
n
i
A
n
i
F
n
−
i
2
ans = F_n^2 - |P| = F_n^2 - \sum_{i=1}^n (-1)^{i+1}C_n^i A_n^i F_{n-i}^2 = C_n^0A_n^0 F_n^2+ \sum_{i=1}^n (-1)^{i}C_n^i A_n^i F_{n-i}^2 = \sum_{i=0}^n (-1)^{i}C_n^i A_n^i F_{n-i}^2
ans=Fn2−∣P∣=Fn2−∑i=1n(−1)i+1CniAniFn−i2=Cn0An0Fn2+∑i=1n(−1)iCniAniFn−i2=∑i=0n(−1)iCniAniFn−i2
所以通过容斥原理我们可以得到上面这个比较漂亮的式子,将两种颜色的组合问题划归到一种颜色的组合问题,由于组合数可以
O
(
n
)
O(n)
O(n) 时间预处理出来,接下来我们好好考虑一种颜色
F
n
F_n
Fn 该如何处理。
边界易得到
F
0
=
1
,
F
1
=
2
F_0 = 1, F_1 = 2
F0=1,F1=2,而上面的分析我们也可以得到
F
n
=
∑
i
=
0
n
C
n
i
A
n
i
F_n = \sum_{i=0}^{n}C_n^i A_n^i
Fn=∑i=0nCniAni, 虽然组合数可以预处理出来,但是对于每一个
n
n
n 我们都要处理一遍,所以总复杂度还是
O
(
n
2
)
O(n^2)
O(n2) 的,所以我们不能用通项公式,需要再看看递推公式。从
F
n
−
1
F_{n-1}
Fn−1 变成
F
n
F_{n}
Fn,左右各增加了一个顶点,总共会有五种情况:
①新增的两个点不参与涂色,这样方案数是
F
n
−
1
F_{n-1}
Fn−1;
②新增的两个点之间涂色,这样方案数是
F
n
−
1
F_{n-1}
Fn−1;
③新增的左边点与右边原来的
n
−
1
n-1
n−1 个点涂色,这样方案数为
(
n
−
1
)
F
n
−
1
(n-1)F_{n-1}
(n−1)Fn−1;
④新增的右边点与左边原来的
n
−
1
n-1
n−1 个点涂色,这样方案数为
(
n
−
1
)
F
n
−
1
(n-1)F_{n-1}
(n−1)Fn−1;
⑤新增的左边点与右边原来的
n
−
1
n-1
n−1 个点涂色 且 新增的右边点与左边原来的
n
−
1
n-1
n−1 个点涂色, 即③④的重复计数,这样方案数为
(
n
−
1
)
2
F
n
−
2
(n-1)^2F_{n-2}
(n−1)2Fn−2;
对于这五种情况的讨论,其实③④⑤也用到了容斥原理,我们可以得到递推式:
F
n
=
2
n
F
n
−
1
−
(
n
−
1
)
2
F
n
−
2
F_n = 2nF_{n-1} - (n-1)^2F_{n-2}
Fn=2nFn−1−(n−1)2Fn−2
这样,
F
n
F_n
Fn 也可以通过
O
(
n
)
O(n)
O(n) 时间计算出来,整个算法的复杂度就是
O
(
n
)
O(n)
O(n),捋一捋,即分为两步,先计算一种颜色的情况,再通过容斥原理计算两种颜色的情况,所以其实三种颜色在这个基础上也是可以继续算的,只不过更加复杂啦。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
const int maxn = 1e7 + 10;
const int INF = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
int inv[maxn], k[maxn], f[maxn];
int n;
int main()
{
scanf("%d", &n);
inv[0] = inv[1] = 1;
for(int i = 2; i <= n; i++) //预处理逆元
inv[i] = 1LL * (mod - mod / i) * inv[mod % i] % mod;
for(int i = 2; i <= n; i++) //inv[i] 现在表示 i!的逆元
inv[i] = 1LL * inv[i-1] * inv[i] % mod;
int fac = 1;
for(int i = 2; i <= n; i++) //fac = n!
fac = 1LL * fac * i % mod;
k[0] = 1; //计算组合数 A_n^i C_n^i
for(int i = 1; i <= n; i++)
{
int tmp = 1LL * fac * inv[n-i] % mod;
k[i] = 1LL * tmp * tmp % mod * inv[i] % mod;
}
f[0] = 1, f[1] = 2; //计算一种颜色的情况
for(int i = 2; i <= n; i++)
f[i] = (2LL * i * f[i-1] - 1LL * (i - 1) * (i - 1) % mod * f[i-2]) % mod;
ll ans = 0; //用容斥原理计算两种颜色的情况
for(int i = 0; i <= n; i++)
{
if(i & 1) //奇数为符号
ans = (ans - 1LL * k[i] * f[n-i] % mod * f[n-i] % mod) % mod;
else
ans = (ans + 1LL * k[i] * f[n-i] % mod * f[n-i] % mod) % mod;
}
if(ans < 0)
ans = ans + mod;
printf("%lld\n", ans);
}