luogu7843. 「PMOI-4」猜排列(乱搞)

题目链接

https://www.luogu.com.cn/problem/P7843

题解

这里给出一种所有子任务下询问问题 2 的次数都不超过 \(13\) 的做法。

朴素的做法是:假设我们想要知道 \([1, m]\) 内的数分别在什么位置,我们可以先询问一次问题 2 来知晓 \((\frac{m}{2}, m]\) 的位置集合,再借助问题 1 用数 \(m + 1\) 去对这些位置上的数分别取模,从而确定出它们各自的值是多少(\(m + 1\) 对 \((\frac{m}{2}, m]\) 内的任意数取模的结果都是不一样的),最后再通过递归来进一步求 \([1, \frac{m}{2}]\) 内的数的位置。这样,我们只需要在最开始先询问一次问题 2 来确定出 \(n\) 的位置,然后利用上述做法求 \([1, n - 1]\) 内的数的位置即可得到整个排列。该方法所需的问题 2 的询问次数为 \(1 + \log n\),当 \(n = 5 \times 10^4\) 时为 \(16\)。

考虑如何优化一次询问。注意到 \(5 \bmod 1 = 0, 5 \bmod 2 = 1, 5 \bmod 3 = 2\),而 \(49999\) 不断(整)除以 \(2\) 恰好会得到 \(6\),也即:当我们想要知道 \([1, 6]\) 内的数分别在什么位置时,我们先确定出 \(4, 5, 6\) 的位置,之后无需再继续递归,而是用 \(5\) 分别对剩下三个未确定位置上的数取模,利用模数就能将 \(1, 2, 3\) 的位置判断出来。这样就节省了一次询问,即在最后一个子任务下只需 \(15\) 次询问 2 即可求出排列。这足以通过本题。

延续上面的思路:如果一个数 \(x\) 除以从 \(1\) 开始的一段连续自然数 \(1 \sim r\) 的余数均不相同,那么利用 \(x\) 就能够把 \(1 \sim r\) 的位置判断出来。又因为 \(x \bmod 1 = 0\),因此 \(x \bmod 2\) 必然为 \(1\),\(x \bmod 3\) 必然为 \(2\)……即 \(x \bmod i = i - 1(1 \leq i \leq r)\)。写一份程序可以找到在 \(50000\) 以内满足这一性质的最佳数 \(x = 27719\),它对应的 \(r = 12\)。也即:如果我们找到了 \(27719\) 的位置,那么当我们递归到 \([1, m]\) 满足 \(m \leq 12\) 时就可以停止使用朴素做法,直接利用 \(27719\) 询问问题 1 就可以确定出 \(1 \sim m\) 所有数的位置。这样当 \(n = 5 \times 10^4\) 时我们在朴素做法的基础上减少了三次递归,问题 2 的询问次数也降至了 \(\log n - 2 = 13\)。

利用 \(27719\) 这一特殊数,子任务 3 和 4 询问问题 2 的次数仍不能得到有效优化,因为这两个子任务的 \(n\) 达不到 \(27719\)。略微缩小查找范围,我们可以找到另一个特殊数 \(2519\),它对应的 \(r = 10\)。使用该数可以使子任务 3 的询问问题 2 的次数降至 \(12\),子任务 4 的询问次数降至 \(11\)。

代码

#include<bits/stdc++.h>

using namespace std;

const int N = 56789;

int n, m1, m2, m3, p2519, p27719, a[N];
bool tag[N];

vector<int> query(vector<int> s, int p) {
  int l = s.size();
  cout << "? " << l;
  for (auto x : s) {
    cout << ' ' << x;
  }
  cout << ' ' << p << '\n';
  cout.flush();
  int n;
  cin >> n;
  vector<int> result(n);
  for (int i = 0; i < n; ++i) {
    cin >> result[i];
  }
  return result;
}

int query(int x, int y) {
  cout << "! " << x << ' ' << y << '\n';
  cout.flush();
  int z;
  cin >> z;
  return z;
}

void solve(int m, int p) {
  vector<int> s;
  for (int i = 1; i <= n; ++i) {
    if (!tag[i]) {
      s.push_back(i);
    }
  }
  if (m == 1) {
    a[s[0]] = 1;
    return;
  }
  if (m <= 12 && p27719) {
    for (auto i : s) {
      a[i] = query(p27719, i) + 1;
    }
    return;
  }
  if (m <= 10 && p2519) {
    for (auto i : s) {
      a[i] = query(p2519, i) + 1;
    }
    return;
  }
  vector<int> x = query(s, (m >> 1) + 1);
  for (auto i : x) {
    tag[i] = true;
    int diff = query(p, i);
    a[i] = diff ? a[p] - diff : (a[p] >> 1);
  }
  for (auto i : x) {
    if (a[i] == (m >> 1) + 1) {
      p = i;
    }
    if (a[i] == 27719) {
      p27719 = i;
    }
    if (a[i] == 2519) {
      p2519 = i;
    }
  }
  solve(m >> 1, p);
}

int main() {
  ios::sync_with_stdio(false);
  cin >> n >> m1 >> m2 >> m3;
  if (n == 4) {
    vector<int> foo = query(vector<int>{1, 2, 3, 4}, 3);
    int x = foo[0], y = foo[1];
    for (int i = 1; i <= 4; ++i) {
      if (i != x && i != y) {
        int z = query(x, i), w = query(y, i);
        if (z == 0 && w == 0) {
          a[i] = 1;
        } else {
          a[i] = 2;
          if (z == 0) {
            a[x] = 4;
            a[y] = 3;
          } else {
            a[x] = 3;
            a[y] = 4;
          }
        }
      }
    }
  } else {
    vector<int> s;
    for (int i = 1; i <= n; ++i) {
      s.push_back(i);
    }
    int p = query(s, n)[0];
    a[p] = n;
    tag[p] = true;
    solve(n - 1, p);
  }
  cout << 'A';
  for (int i = 1; i <= n; ++i) {
    cout << ' ' << a[i];
  }
  cout << '\n';
  cout.flush();
  return 0;
}
上一篇:P1965 [NOIP2013 提高组] 转圈游戏


下一篇:题解 P6688 可重集