题目链接:二分图染色
Description
给定一个完全二分图,图的左右两边顶点数目相同。
每条边我们都要染成红、绿、蓝中的一种。
要求满足任意两条红边不共享端点,任意两条蓝边不共享端点。
求出所有满足条件的染色方案数,答案对(1e9+7)取模。
注:(n)表示二分图其中一边的点数目。
数据范围 (1le nle 10^7)
Solution
我们切换思路,先默认所有的边都是绿色的,这些边的端点没有任何限制。
接下来,我们尝试将部分边给染成红、蓝色。
我们定义 (f_i) 表示二分图其中一边有(i)个点,并且边只有绿色和红色时的答案。
显然,边界条件为 (f_0 = 1, f_1 = 2)。
我们考虑如何递推这个 (f) 序列, 注意到 (f_i) 从 (f_{i-1}) 转移过来会新增两个点。
我们分五种情况讨论:
- 1 这两个点出度为(0),那么方案数是 (f_{i-1})
- 2 这两个点之间连边,那么方案数是 (f_{i-1})
- 3 左点有出度,但不连向右点,那么方案数是 ((i-1)*f_{i-1})
- 4 右点有出度,但不连向左点,那么方案数是 ((i-1)*f_{i-1})
- 5 左点有出度,但不连向右点,右点有出度,但不连向左点,这一部分是3和4的重复部分,需要减掉。方案数是 ((i-1)^2 f_{i-2})
综上,(f_i=f_{i-1}+f_{i-1}+(i-1)*f_{i-1}+(i-1)*f_{i-1}-(i-1)^2* f_{i-2}=2i*f_{i-1}-(i-1)^2*f_{i-2})
于是我们可以在(O(n))的复杂度内预处理出 (f) 序列。
好,那么我们再考虑加入蓝色边,注意到蓝色边和红色边本质相同,因此答案一定是个对称的式子。
你会发现这是一个(n)元的容斥计数,答案为:
[ans=sum_{i=0}^{n}(-1)^i*C_n^i*C_n^{n-i}*i!*f_{n-i}* f_{n-i}
]
[ans=sum_{i=0}^{n}(-1)^i*C_n^i*A_n^i*f_{n-i}^2
]
反思
这一道题的瓶颈在于如何寻找突破口。
我们先固定了两种颜色去考虑,然后再加入新的颜色,并用容斥的思想去排斥和添加新的值。
同时,我们也要分析(f_i)和(f_{i-1})之间对应的所有可能关系,新增两个点的所有可能连边情况,这样才能跨过这个坎。
数列网站(oeis)固然有用,但是我们要学会自己现推的本领,要冷静分析数列间的微妙关系。
另外,做题不取模,爆零两行泪~
做题不开long long,依旧爆零两行泪233~
Code
// Author: wlzhouzhuan
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
inline int read() {
int x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(int x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
const int N = 10000001;
const int mod = 1e9 + 7;
int fac[N], invf[N];
int qpow(int a, int b) {
int ret = 1;
while (b > 0) {
if (b & 1) ret = 1ll * ret * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return ret;
}
void pre(int n) {
fac[0] = invf[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
invf[n] = qpow(fac[n], mod - 2);
for (int i = n - 1; i >= 1; i--) invf[i] = 1ll * invf[i + 1] * (i + 1) % mod;
}
int C(int n, int m) {
return n >= m ? 1ll * fac[n] * invf[n - m] % mod * invf[m] % mod : 0;
}
int f[N], n;
int main() {
n = read();
pre(n);
f[0] = 1, f[1] = 2;
for (int i = 2; i <= n; i++) f[i] = (2ll * i * f[i - 1] % mod - 1ll * (i - 1) * (i - 1) % mod * f[i - 2] % mod + mod) % mod;
#ifdef debug
for (int i = 0; i <= n; i++) {
printf("f[%d] = %d
", i, f[i]);
}
#endif
int ans = 0;
for (int i = 0; i <= n; i++) {
int opt = (i & 1) ? -1 : 1;
int res = (1ll * C(n, i) * C(n, i) % mod * fac[i] % mod * f[n - i] % mod * f[n - i] % mod) % mod;
ans = (ans + 1ll * opt * res % mod + mod) % mod;
}
printf("%d
", ans);
return 0;
}