zoukankan      html  css  js  c++  java
  • [牛客13229] 二分图染色

    题目链接:二分图染色

    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; 
    }
    
    
  • 相关阅读:
    git命令
    WPF让绑定支持表达式
    WPF多语言动态切换的一种方法
    C#监测系统事件,睡眠状态
    记一次渗透测试
    Relay
    ECShop相关漏洞复现/分析
    人工智能学习路线图
    抽奖算法
    关于微信开发的 appid,openid,unionid
  • 原文地址:https://www.cnblogs.com/wlzhouzhuan/p/12669318.html
Copyright © 2011-2022 走看看