[组合容斥] 美团2017年CodeM大赛 二分图染色

题面

   link 给定一个完全二分图,图的左右两边的顶点数目相同,都是 n n n。我们要给图中的每条边染成红色、蓝色、或者绿色,并使得任意两条红边不共享端点、同时任意两条蓝边也不共享端点。
   计算所有满足条件的染色的方案数,并对 1 0 9 + 7 10^9+7 109+7 取模,其中 n ≤ 1 e 7 n ≤ 1e7 n1e7

分析

   完全二分图是指左边的每一个点与右边的每一个点都有连线。其实这题涂成绿色可以看成不涂,我们只需要考虑涂成红色和蓝色的情况。
   经过简单思考,我们发现只涂一种颜色是比较简单的,若是一边顶点数是 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=Fn2P, 而根据容斥原理:
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=P11P12...P1n...Pnn=PijPi1j1Pi2j2...+(1)k+1Pi1j1Pi2j2...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 Cn1An1Fn12Cn2An2Fn22...+(1)n+1Cn2An2F02=i=1n(1)i+1CniAniFni2, 于是可以得到:
   
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=Fn2P=Fn2i=1n(1)i+1CniAniFni2=Cn0An0Fn2+i=1n(1)iCniAniFni2=i=0n(1)iCniAniFni2
   
    所以通过容斥原理我们可以得到上面这个比较漂亮的式子,将两种颜色的组合问题划归到一种颜色的组合问题,由于组合数可以 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} Fn1 变成 F n F_{n} Fn,左右各增加了一个顶点,总共会有五种情况:
    ①新增的两个点不参与涂色,这样方案数是 F n − 1 F_{n-1} Fn1
    ②新增的两个点之间涂色,这样方案数是 F n − 1 F_{n-1} Fn1;
    ③新增的左边点与右边原来的 n − 1 n-1 n1 个点涂色,这样方案数为 ( n − 1 ) F n − 1 (n-1)F_{n-1} (n1)Fn1
    ④新增的右边点与左边原来的 n − 1 n-1 n1 个点涂色,这样方案数为 ( n − 1 ) F n − 1 (n-1)F_{n-1} (n1)Fn1
    ⑤新增的左边点与右边原来的 n − 1 n-1 n1 个点涂色 且 新增的右边点与左边原来的 n − 1 n-1 n1 个点涂色, 即③④的重复计数,这样方案数为 ( n − 1 ) 2 F n − 2 (n-1)^2F_{n-2} (n1)2Fn2
    对于这五种情况的讨论,其实③④⑤也用到了容斥原理,我们可以得到递推式:
F n = 2 n F n − 1 − ( n − 1 ) 2 F n − 2 F_n = 2nF_{n-1} - (n-1)^2F_{n-2} Fn=2nFn1(n1)2Fn2
    这样, 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值