题意
给一个 个点的完全二分图(即有 条边),每条边可以染红色,蓝色,绿色。一个点不能连出超过一条红色边,也不能连出超过一条蓝色边。问将这些边染色的方案数有多少种,答案对 取模。()
分析
给这题跪了!我还是太菜了。
完全二分图可以转化为一个 的矩阵,第 行第 列的点表示左边第 个点和右边第 个点。如果我们把染绿色看成不染色,这样子,问题可以转化为: 的矩阵,每个点可以染红色或蓝色,每一行每一列最多有一个红色,每一行每一列最多有一个蓝色。求涂色方案数。
同时有两种颜色很烦,要是能只考虑一种颜色就好了!我们不妨看看两种颜色的方案能不能由一种颜色的推过来。
设 表示只有红色的方案数, 表示有红有蓝的方案数。
那么 吗?
并不是,因为这样子一个点可能既涂了红色又涂了蓝色,我们要把它减掉。于是这就涉及容斥原理了。
我们设 表示至少有特定的 个点既涂了红色又涂了蓝色的方案数。
那么我们把这 行 列抽出来,剩下 行和 列可以随便涂,于是
这 行 列的选法有 种,于是我们得到容斥式: 。
这告诉了我们,只要得到一种颜色的涂法,就可以得到两种颜色的涂法!!!
于是我们接下来来看只有红色涂法的方案数怎么求。
有一个容易想的求法,就是选 个点来涂色,即:
不过这样做必然 ,因为复杂度是 的。
让我们看看多出来的这一行一列来涂色,总共有 个点。
这里盗一下邓老师的图!!!!
考虑只涂一个,即在最后一行或最后一列涂:
不考虑重复的话(就是随便放),有 种涂法,再加上不涂,有 种涂法,总共是。
但是我们每涂一个,意味着那一行或那一列都不能再涂了,于是那一行有 种重复方案(选一列来和重复),那么总共重复了 次,由于可以选 行,所以有 次。每一列也可以进行同样的操作,于是涂一个的方案:
考虑涂两个,即最后一行和最后一列都涂:
那我们只用抽出那一行一列即可。方案数为
于是我们把这些加起来,可以得到递推式:
总体评价
好难啊啊啊啊!!不过感谢邓老师让我学会这题!!
代码如下
#include <bits/stdc++.h> #include<ext/pb_ds/hash_policy.hpp> #include<ext/pb_ds/assoc_container.hpp> #define N 10000005 using namespace __gnu_pbds; const int mod = 1e9 + 7; using namespace std; typedef long long LL; typedef unsigned long long uLL; struct custom_hash { static uint64_t splitmix64(uint64_t x) { x += 0x9e3779b97f4a7c15; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; x = (x ^ (x >> 27)) * 0x94d049bb133111eb; return x ^ (x >> 31); } size_t operator()(uint64_t x) const { static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count(); return splitmix64(x + FIXED_RANDOM); } }; LL z = 1; int read(){ int x, f = 1; char ch; while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1; x = ch - '0'; while(ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + ch - 48; return x * f; } int ksm(int a, int b, int p){ int s = 1; while(b){ if(b & 1) s = z * s * a % p; a = z * a * a % p; b >>= 1; } return s; } int f[N], fac[N], inv[N], ans; int C(int n, int m){ return z * fac[n] * inv[m] % mod * inv[n - m] % mod; } int A(int n, int m){ return z * fac[n] * inv[n - m] % mod; } int main(){ int i, j, n, m; n = read(); for(fac[0] = i = 1; i <= n; i++) fac[i] = z * fac[i - 1] * i % mod; inv[n] = ksm(fac[n], mod - 2, mod); for(i = n - 1; i >= 0; i--) inv[i] = z * inv[i + 1] * (i + 1) % mod; f[0] = 1, f[1] = 2; for(i = 2; i <= n; i++) f[i] = (z * 2 * i * f[i - 1] % mod - z * (i - 1) * (i - 1) % mod * f[i - 2] % mod) % mod; for(i = 0; i <= n; i++){ if(i % 2) ans = (ans - z * C(n, i) * A(n, i) % mod * f[n - i] % mod * f[n - i] % mod) % mod; else ans = (ans + z * C(n, i) * A(n, i) % mod * f[n - i] % mod * f[n - i] % mod) % mod; } ans = (ans + mod) % mod; printf("%d", ans); return 0; }