先选出和睦情侣与座位行,再用完全不和睦排座数递推处理剩余情侣。
OJ: luogu
题目 ID: P4931
难度:普及+/提高
标签:组合计数递推数学
日期: 2026-06-22 23:15
题意
有 n 对情侣和 n 排座位,每排两个座位。所有人坐满后,如果一对情侣坐在同一排,就称这对情侣和睦。问恰好 k 对情侣和睦的排座方案数。
思路
朴素做法是枚举所有 (2n)! 种座位排列,再统计和睦情侣数量。
先看一个可以直接验证想法的朴素解:
cpp
#include <bits/stdc++.h>
using namespace std;
// brute.cpp:枚举所有人的座位排列,只适合 n<=5 的小数据验证。
int count_harmony(const vector<int> &seat_person, int n) {
int row_of_person[12];
for (int seat = 0; seat < 2 * n; seat++) {
int person = seat_person[seat];
row_of_person[person] = seat / 2;
}
int cnt = 0;
for (int i = 0; i < n; i++) {
if (row_of_person[2 * i] == row_of_person[2 * i + 1]) {
cnt++;
}
}
return cnt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n, k;
cin >> n >> k;
vector<int> persons;
for (int i = 0; i < 2 * n; i++) {
persons.push_back(i);
}
long long answer = 0;
do {
if (count_harmony(persons, n) == k) {
answer++;
}
} while (next_permutation(persons.begin(), persons.end()));
cout << answer % 998244353 << '\n';
}
return 0;
}大数据下需要组合计数。设 g[i] 表示 i 对情侣坐 i 排座位,并且没有任何一对情侣同排的方案数。
这个数组可以递推预处理:
text
g[0] = 1
g[1] = 0
g[i] = 4*i*(i-1) * (g[i-1] + 2*(i-1)*g[i-2])它的作用是处理“剩余情侣完全不和睦”的部分。
现在统计恰好 k 对和睦:
- 从
n对情侣中选出和睦的k对:C(n,k); - 从
n排座位中选出给这些情侣的k排:C(n,k); - 把这
k对情侣匹配到这k排:k!; - 每对情侣在同一排内有左右两种坐法:
2^k; - 剩余
n-k对情侣和剩余n-k排不能再产生和睦:g[n-k]。
因此答案为:
text
C(n,k)^2 * k! * 2^k * g[n-k]代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
struct Ask {
int n, k;
};
vector<Ask> asks;
vector<int> factorial_value;
vector<int> inverse_factorial;
vector<int> pow_two;
vector<int> no_harmony; // no_harmony[i] 表示 i 对情侣、i 排座位时没有任何和睦情侣的方案数。
long long fast_power(long long a, long long b) {
long long result = 1;
while (b > 0) {
if (b & 1) {
result = result * a % MOD;
}
a = a * a % MOD;
b >>= 1;
}
return result;
}
long long combination(int n, int k) {
if (k < 0 || k > n) {
return 0;
}
return (long long)factorial_value[n] * inverse_factorial[k] % MOD * inverse_factorial[n - k] % MOD;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
asks.resize(t);
int max_n = 0;
for (int i = 0; i < t; i++) {
cin >> asks[i].n >> asks[i].k;
max_n = max(max_n, asks[i].n);
}
factorial_value.assign(max_n + 1, 1);
inverse_factorial.assign(max_n + 1, 1);
pow_two.assign(max_n + 1, 1);
no_harmony.assign(max_n + 1, 0);
for (int i = 1; i <= max_n; i++) {
factorial_value[i] = (long long)factorial_value[i - 1] * i % MOD;
pow_two[i] = (long long)pow_two[i - 1] * 2 % MOD;
}
if (max_n >= 0) {
inverse_factorial[max_n] = fast_power(factorial_value[max_n], MOD - 2);
for (int i = max_n; i >= 1; i--) {
inverse_factorial[i - 1] = (long long)inverse_factorial[i] * i % MOD;
}
}
no_harmony[0] = 1;
if (max_n >= 1) {
no_harmony[1] = 0;
}
for (int i = 2; i <= max_n; i++) {
// 取一对情侣中的一个人所在行分类,递推出没有任何情侣同排的排座方案数。
long long part = (no_harmony[i - 1] + 2LL * (i - 1) % MOD * no_harmony[i - 2]) % MOD;
no_harmony[i] = (int)(4LL * i % MOD * (i - 1) % MOD * part % MOD);
}
for (int i = 0; i < t; i++) {
int n = asks[i].n;
int k = asks[i].k;
long long answer = combination(n, k) * combination(n, k) % MOD;
answer = answer * factorial_value[k] % MOD * pow_two[k] % MOD;
answer = answer * no_harmony[n - k] % MOD;
cout << answer << '\n';
}
return 0;
}复杂度
预处理时间
空间复杂度
总结
恰好 k 对和睦的问题,关键是先把这 k 对固定出来,再把剩余部分变成“完全不和睦”的子问题。g[i] 预处理好后,每个询问就是一条组合公式。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
