题目链接
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;
}