1. 程式人生 > >luogu4931.情侶?給我燒了!(加強版)(錯位排列)

luogu4931.情侶?給我燒了!(加強版)(錯位排列)

check 除了 har max www. 預處理 urn amp 答案

題目鏈接

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

題解

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

對於每一個 \(k\),滿足恰好有 \(k\) 對情侶和睦的方案數為

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

其中,\(f_x\) 表示 \(x\) 對情侶坐 \(x\) 排座位且沒有任何一對情侶坐在同一排的方案數。

上述式子的意義為:從 \(n\) 對情侶中選出 \(k\) 對作為和睦的,再從 \(n\) 排中選出 \(k\) 排供這 \(k\) 對情侶就坐,方案數為 \(\binom{n}{k} × \binom{n}{k}\)

。由於 \(k\) 對情侶就坐順序可以不一致,且每隊情侶內部雙方也可以交換座位,因此方案數為 \(k! × 2^k\)。再乘以剩下的 \(n - k\) 對情侶均不出現和睦的方案數即為最終答案。

現在問題在於如何求出 \(f_x\)。考慮容斥,\(f_x=\) 任意就坐的方案數\(-\)至少存在一對情侶和睦的方案數\(+\)至少存在兩對情侶和睦的方案數\(-\)至少存在三對情侶和睦的方案數\(...\)

那麽做和上面類似的分析,我們可以得到:

\[f_x = \sum_{i = 0}^{x} (-1)^i × \binom{x}{i} × \binom{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 \times (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\n", 1ll * binom(n, k) * binom(n, k) % mod * fac[k] % mod * pow2[k] % mod * f[n - k] % mod);
    }
    return 0;
}

luogu4931.情侶?給我燒了!(加強版)(錯位排列)