[牛客13229] 二分图染色

题目链接:二分图染色

Description

给定一个完全二分图,图的左右两边顶点数目相同。
每条边我们都要染成红、绿、蓝中的一种。
要求满足任意两条红边不共享端点,任意两条蓝边不共享端点。
求出所有满足条件的染色方案数,答案对\(1e9+7\)取模。
注:\(n\)表示二分图其中一边的点数目。
数据范围 \(1\le n\le 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\n", 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\n", ans);
  return 0; 
}

posted @ 2020-04-09 20:24  wlzhouzhuan  阅读(265)  评论(0编辑  收藏  举报