会议座位

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

先把打乱后的座位顺序映射成原座位下标序列,再把不满值转化成这个整数序列的逆序对数量。

OJ: luogu

题目 ID: P5149

难度:普及+/提高

标签:归并排序逆序对字符串哈希

日期: 2026-06-21 15:31

题意

先给出老师们原来的座位顺序,再给出打乱后的座位顺序。

如果有一对老师 ab,原来是 ab 左边,现在变成 ab 右边,那么这对老师贡献 1 单位不满值。

要求输出总不满值。

思路

先看一个可以直接验证想法的朴素解:

cpp
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN = 2005;

int n;
int a[MAXN];
map<string, int> pos;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        string name;
        cin >> name;
        pos[name] = i;
    }

    for (int i = 1; i <= n; i++) {
        string name;
        cin >> name;
        a[i] = pos[name];
    }

    ll answer = 0;

    // brute.cpp:转成原序列下标后,直接枚举所有下标对统计逆序对。
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            if (a[i] > a[j]) {
                answer++;
            }
        }
    }

    cout << answer << '\n';
    return 0;
}

如果直接在打乱后的序列里枚举所有老师对,判断他们相对顺序是否和原来相反,复杂度是 O(n2)O(n^2),肯定不行。

关键是把名字问题转成整数问题。

先把原来的座位顺序记下来:

  • 第一个老师的位置记为 1
  • 第二个老师的位置记为 2

用一个 map<string, int> 记录“名字 -> 原位置”。

然后把打乱后的序列改写成“原位置编号序列”。

例如原序列是:

text
Stan Kyle Kenny

位置编号就是:

text
Stan -> 1, Kyle -> 2, Kenny -> 3

打乱后如果是:

text
Kyle Stan Kenny

就会变成:

text
2 1 3

此时题目要求的“不满值”,就恰好变成这个整数序列里的逆序对数量。

所以整题转化成标准逆序对问题,再用归并排序统计即可。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN = 100005;

int n;
int a[MAXN];
int tmp[MAXN];
ll answer;
map<string, int> pos;

void merge_sort_count(int l, int r) {
    if (l >= r) {
        return;
    }

    int mid = (l + r) >> 1;
    merge_sort_count(l, mid);
    merge_sort_count(mid + 1, r);

    int i = l;
    int j = mid + 1;
    int k = l;

    while (i <= mid && j <= r) {
        if (a[i] <= a[j]) {
            tmp[k++] = a[i++];
        }
        else {
            tmp[k++] = a[j++];
            answer += (ll)(mid - i + 1);
        }
    }

    while (i <= mid) {
        tmp[k++] = a[i++];
    }
    while (j <= r) {
        tmp[k++] = a[j++];
    }

    for (int p = l; p <= r; p++) {
        a[p] = tmp[p];
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        string name;
        cin >> name;
        pos[name] = i;
    }

    for (int i = 1; i <= n; i++) {
        string name;
        cin >> name;
        a[i] = pos[name];
    }

    answer = 0;
    merge_sort_count(1, n);

    cout << answer << '\n';
    return 0;
}

复杂度

  • 建立名字到位置的映射:O(nlogn)O(n log n)
  • 归并排序统计逆序对:O(nlogn)O(n log n)

总时间复杂度:O(nlogn)O(n log n),空间复杂度:O(n)O(n)

总结

这题表面上是字符串和座位顺序,核心其实是:

  1. 用映射把名字转成原始排名编号
  2. 把“相对顺序颠倒”改写成逆序对
  3. 用归并排序统计答案

看出“先映射,再数逆序对”,题目就回到熟悉模板了。