题解:P15652 [省选联考 2026] 排列游戏 / perm

· · 题解

需要注意到,本题的构造目标是不同于初始排列 p 的另一个排列 q,需要满足对于任意区间,初始排列和构造排列的 \operatorname{mex} 相同。这是因为在某些情况下,仅通过区间 \operatorname{mex} 是无法确定排列本身的,例如 0 3 2 10 2 3 1

本题关键在于:对于一个排列,其某个位置序列的 \operatorname{mex} 等于位置序列补集\min,这是因为 \operatorname{mex} 本质上就是位置序列上缺失的数字中最小的一个,而缺失的所有数字都在位置序列的补集中给出了。对于区间 \operatorname{mex} 而言,它的补集就是一段前缀和一段后缀,因此设 A_i 表示下标在 [0, i] 范围内元素的最小值,而 B_i 表示下标在 [i, n - 1] 范围内元素的最小值,则有

\operatorname{mex}_{i=l}^r (p_i) = \min (A_{l - 1}, B_{r + 1})

因此,只要我们构造的排列对应的前缀 \min 和后缀 \min 和初始排列相同,那么构造结果就是正确的。

需要额外注意 0 在边界时可能出现的询问问题。剩余的细节请参考代码。

::::info[代码]

#include "perm.h"
#include <algorithm>
using namespace std;

void init(int c, int t) {
  return;
}

vector<int> perm(int n) {
  vector<bool> used(n, false);
  vector<int> ans(n, -1);
  vector<pair<int, int>> pos;

  int last = -1, p0 = -1;
  for (int i = 0; i < n - 1; i ++) {
    int res = query(i + 1, n - 1);
    if (last != res) {
      last = res;
      ans[i] = res;
      used[res] = true;
      if (res == 0) {
        p0 = i;
        break;
      }
      pos.push_back({res, i});
    }
  }

  if (last != 0) {
    used[0] = true;
    ans[n - 1] = 0;
    p0 = n - 1;
  } else {
    last = -1;
    for (int i = n - 1; i > p0; i --) {
      int res = query(0, i - 1);
      if (last != res) {
        last = res;
        ans[i] = res;
        used[res] = true;
        pos.push_back({res, i});
      }
    }
  }

  int ptr = 0, l = p0 - 1, r = p0 + 1;

  auto gnext = [&] () {
    while (used[ptr]) ++ ptr;
    used[ptr] = true;
    return ptr;
  };

  pos.push_back({n, -1});
  pos.push_back({n + 1, n});
  sort(pos.begin(), pos.end());
  for (auto ele : pos) {
    int p = ele.second;
    if (p <= l) {
      while (l > p) ans[l --] = gnext();
      -- l;
    } else {
      while (r < p) ans[r ++] = gnext();
      ++ r;
    }
  }

  return ans;
}

::::