zoukankan      html  css  js  c++  java
  • luogu4931. 情侣?给我烧了!(加强版)(错位排列)

    题目链接

    https://www.luogu.org/problemnew/show/P4931

    题解

    以下部分是我最开始的想法。

    对于每一个 (k),满足恰好有 (k) 对情侣和睦的方案数为

    [inom{n}{k} × inom{n}{k} × k! × 2^k × f_{n - k} ]

    其中,(f_x) 表示 (x) 对情侣坐 (x) 排座位且没有任何一对情侣坐在同一排的方案数。

    上述式子的意义为:从 (n) 对情侣中选出 (k) 对作为和睦的,再从 (n) 排中选出 (k) 排供这 (k) 对情侣就坐,方案数为 (inom{n}{k} × inom{n}{k})。由于 (k) 对情侣就坐顺序可以不一致,且每队情侣内部双方也可以交换座位,因此方案数为 (k! × 2^k)。再乘以剩下的 (n - k) 对情侣均不出现和睦的方案数即为最终答案。

    现在问题在于如何求出 (f_x)。考虑容斥,(f_x=) 任意就坐的方案数(-)至少存在一对情侣和睦的方案数(+)至少存在两对情侣和睦的方案数(-)至少存在三对情侣和睦的方案数(...)

    那么做和上面类似的分析,我们可以得到:

    [f_x = sum_{i = 0}^{x} (-1)^i × inom{x}{i} × inom{x}{i} × i!× 2^i × (2×(x - i))! ]

    上述式子最后的 ((2×(x - i))!) 表示不强制和睦的 (x - i) 对情侣随意就坐的方案数。

    总预处理的复杂度为 (O(n^2))

    虽然这种容斥的想法很自然,但是时间复杂度并不优秀。我们需要找更好的办法求 (f_x)于是就有了加强版。

    在此之前,我们先回忆一下错位排列数的递推式:

    [D_n = (n - 1)(D_{n - 1} + D_{n - 2})(n geq 2) ]

    《组合数学》上对其的证明大概是这样的:

    我们想要求得 (D_n),考虑长度为 (n) 的排列的第一个位置,有 (2, 3, 4, cdots, n)(n - 1) 种填法。很显然的是,无论以谁作为开头,方案数都是一样的,因此我们只需算出以任意一个数开头的方案数,乘以 (n - 1) 就好了。我们假设第一位填了 (2),那么第二个位置可以填 (1, 3, 4, cdots, n)。如果填了 (1),那么剩下的部分就是一个 (3, 4, cdots , n) 的错位排列,方案数为 (D_{n - 2});如果不填 (1),那么这一位的 (1)(3, 4, cdots, n) 共同构成了长度为 (n - 1) 的错位排列,方案数为 (D_{n - 1}),因此有 (D_n = (n - 1)(D_{n - 1} + D_{n - 2})(n geq 2))

    我们将其推广到这个题。若 (n) 对情侣要坐 (n) 排座位,考虑第一排座位必须坐两个不互为情侣的人,共有 (2n imes (2n - 2) = 4n(n - 1)) 种方案(第一个人可以是 (2n) 个人里面的任意一个,第二个人可以是剩下的 (2n - 1) 个人中除了第一个人的配偶的任意一个)。接下来共有两种情况:若第一排的两个人对应的配偶也坐到了同一排,那么剩下的 (n - 2) 对情侣就坐且不满足同排互为情侣的方案数为 (f_{n - 2}),由于第一排的两个人对应的配偶可以任选 (n - 1) 排中的一排就坐,且可以互换位置,因此该情况下总方案数为 (2(n - 1)f_{n - 2});若第一排的两个人对应的配偶没有坐到同一排,那么我们可以把他们也视为一对情侣,他们和剩下的 (n - 2) 对情侣就坐合法的方案数为 (f_{n - 1})

    这样,我们就得到了 (f) 的递推式:(f_n = 4n(n - 1)(f_{n - 1} + 2(n - 1)f_{n - 2})(n geq 2)),边界为 (f_0 = 1, f_1 = 0)。此题就可以在 (O(n)) 的时间内完成预处理了。

    代码

    #include<bits/stdc++.h>
    
    using namespace std;
    
    #define X first
    #define Y second
    #define mp make_pair
    #define pb push_back
    #define debug(...) fprintf(stderr, __VA_ARGS__)
    
    typedef long long ll;
    typedef long double ld;
    typedef unsigned int uint;
    typedef pair<int, int> pii;
    typedef unsigned long long ull;
    
    template<typename T> inline void read(T& x) {
      char c = getchar();
      bool f = false;
      for (x = 0; !isdigit(c); c = getchar()) {
        if (c == '-') {
          f = true;
        }
      }
      for (; isdigit(c); c = getchar()) {
        x = x * 10 + c - '0';
      }
      if (f) {
        x = -x;
      }
    }
    
    template<typename T, typename... U> inline void read(T& x, U&... y) {
      read(x), read(y...);
    }
    
    template<typename T> inline bool checkMax(T& a, const T& b) {
      return a < b ? a = b, true : false;
    }
    
    template<typename T> inline bool checkMin(T& a, const T& b) {
      return a > b ? a = b, true : false;
    }
    
    const int N = 5e6 + 10, mod = 998244353;
    
    inline void mul(int& x, int y) {
      x = 1ll * x * y % mod;
    }
    
    inline int qpow(int v, int p) {
      int res = 1;
      for (; p; p >>= 1, mul(v, v)) {
        if (p & 1) {
          mul(res, v);
        }
      }
      return res;
    }
    
    int fac[N], invfac[N], pow2[N], f[N];
    
    inline int binom(int n, int m) {
      return 1ll * fac[n] * invfac[m] % mod * invfac[n - m] % mod;
    }
    
    void init(int n) {
      fac[0] = invfac[0] = pow2[0] = f[0] = 1;
      for (register int i = 1; i <= n; ++i) {
        fac[i] = 1ll * fac[i - 1] * i % mod;
        pow2[i] = (pow2[i - 1] << 1) % mod;
        if (i > 1) {
          f[i] = 4ll * i * (i - 1) % mod * (f[i - 1] + 2ll * (i - 1) * f[i - 2] % mod) % mod;
        }
      }
      invfac[n] = qpow(fac[n], mod - 2);
      for (register int i = n - 1; i; --i) {
        invfac[i] = 1ll * invfac[i + 1] * (i + 1) % mod;
      }
    }
    
    int main() {
      init(5000000);
      int t; read(t);
      for (register int kase = 1; kase <= t; ++kase) {
        int n, k; read(n, k);
        printf("%lld
    ", 1ll * binom(n, k) * binom(n, k) % mod * fac[k] % mod * pow2[k] % mod * f[n - k] % mod);
      }
      return 0;
    }
    
  • 相关阅读:
    Android 在资源文件(res/strings.xml)定义一维数组,间接定义二维数组
    Android 设计模式之MVC模式
    Android Studio 1.0首次安装遇到的问题,无法下载SDK
    android 项目中规范使用SharedPreferences
    Android 在布局容器中动态添加控件
    Android ble 蓝牙4.0 总结一
    Mac Android签名生成keystore
    Android ndk 加载简单的gif 图像
    Android EventBus
    c语言内存
  • 原文地址:https://www.cnblogs.com/ImagineC/p/9809934.html
Copyright © 2011-2022 走看看