[MtOI2018] 情侣?给我烧了!(加强版)

GitHub跳转原题关系图返回列表

先选出和睦情侣与座位行,再用完全不和睦排座数递推处理剩余情侣。

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 对和睦:

  1. n 对情侣中选出和睦的 k 对:C(n,k)
  2. n 排座位中选出给这些情侣的 k 排:C(n,k)
  3. 把这 k 对情侣匹配到这 k 排:k!
  4. 每对情侣在同一排内有左右两种坐法:2^k
  5. 剩余 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;
}

复杂度

预处理时间 O(maxn)O(max n),每个询问 O(1)O(1)

空间复杂度 O(maxn)O(max n)

总结

恰好 k 对和睦的问题,关键是先把这 k 对固定出来,再把剩余部分变成“完全不和睦”的子问题。g[i] 预处理好后,每个询问就是一条组合公式。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析