点此传送到题目
题目传送门↑,要做题的赶快去!学习好就去!
题目描述
有 nn 对情侣来到电影院观看电影。在电影院,恰好留有 nn 排座位,每排包含 22 个座位,共 2×n2×n 个座位。
现在,每个人将会随机坐在某一个位置上,且恰好将这 2 × n2×n 个座位坐满。
如果一对情侣坐在了同一排的座位上,那么我们称这对情侣是和睦的。
你的任务是求出共有多少种不同的就坐方案满足恰好有 kk 对情侣是和睦的。
两种就坐方案不同当且仅当存在一个人在两种方案中坐在了不同的位置。不难发现,在没有任何限制条件的情况下,每个人任意就坐一共会有 (2n)![(2n)的阶乘] 种不同的就坐方案。
题解
以下部分是我最开始的想法。
对于每一个 k,满足恰好有 k 对情侣和睦的方案数为
(nk)×(nk)×k!×2k×fn−k
其中,fx 表示 x 对情侣坐 x 排座位且没有任何一对情侣坐在同一排的方案数。
上述式子的意义为:从 n对情侣中选出 k 对作为和睦的,再从 n 排中选出 k 排供这 k 对情侣就坐,方案数为 (nk)×(nk)。由于 kk 对情侣就坐顺序可以不一致,且每队情侣内部双方也可以交换座位,因此方案数为 k!×2k。再乘以剩下的 n−k 对情侣均不出现和睦的方案数即为最终答案。
现在问题在于如何求出 fx。考虑容斥,fx= 任意就坐的方案数−至少存在一对情侣和睦的方案数+至少存在两对情侣和睦的方案数−至少存在三对情侣和睦的方案数…
那么做和上面类似的分析,我们可以得到:
fx=∑i=0x(−1)i×(xi)×(xi)×i!×2i×(2×(x−i))!
上述式子最后的 (2×(x−i))! 表示不强制和睦的 x−i 对情侣随意就坐的方案数。
总预处理的复杂度为 O(n2)。
虽然这种容斥的想法很自然,但是时间复杂度并不优秀。我们需要找更好的办法求 fxfx。
在此之前,我们先回忆一下错位排列数的递推式:
Dn=(n−1)(Dn−1+Dn−2)(n≥2)
《组合数学》上对其的证明大概是这样的:
我们想要求得 Dn,考虑长度为 n 的排列的第一个位置,有 2,3,4,⋯,n2,3,4,⋯,n 共 n−1 种填法。很显然的是,无论以谁作为开头,方案数都是一样的,因此我们只需算出以任意一个数开头的方案数,乘以 n−1 就好了。我们假设第一位填了 22,那么第二个位置可以填 1,3,4,⋯,n。如果填了 11,那么剩下的部分就是一个 3,4,⋯,n3 的错位排列,方案数为 Dn−2Dn−2;如果不填 11,那么这一位的 11 和 3,4,⋯,n 共同构成了长度为 n−1 的错位排列,方案数为 Dn−1,因此有 Dn=(n−1)(Dn−1+Dn−2)(n≥2)。
我们将其推广到这个题。若 n 对情侣要坐 n 排座位,考虑第一排座位必须坐两个不互为情侣的人,共有 2n×(2n−2)=4n(n−1) 种方案(第一个人可以是 2n 个人里面的任意一个,第二个人可以是剩下的 2n−1 个人中除了第一个人的配偶的任意一个)。接下来共有两种情况:若第一排的两个人对应的配偶也坐到了同一排,那么剩下的 n−2n−2 对情侣就坐且不满足同排互为情侣的方案数为 fn−2,由于第一排的两个人对应的配偶可以任选 n−1n−1 排中的一排就坐,且可以互换位置,因此该情况下总方案数为 2(n−1)fn−2;若第一排的两个人对应的配偶没有坐到同一排,那么我们可以把他们也视为一对情侣,他们和剩下的 n−2 对情侣就坐合法的方案数为 fn−1。
这样,我们就得到了 ff 的递推式:fn=4n(n−1)(fn−1+2(n−1)fn−2)(n≥2),边界为 f0=1,f1=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;
}