题目链接:https://codeforces.com/contest/1586/problem/D
题目大意:
有一个 \(1 \sim n\) 的排列 \(p\),你最多询问 \(2n\) 轮,每轮你可以输入一个长度为 \(n\) 的数列 \(a\),要求 \(1 \le a_i \le n\)(但 \(a\) 不需要是一个 \(1 \sim n\) 的排列)。
对于你每次输入的数列 \(a\),程序会产生一个数列 \(s\),其中 \(s_i = p_i + a_i\),然后程序会返回给你一个出现过至少两次的 \(s_k\) 对应的最小坐标 \(k\)。若没有这个坐标 \(k\) 则返回 \(0\)。
要求在不超过 \(2n\) 轮求出原始的数列 \(p\)。
解题思路:
第 \(p(1 \le p \le n)\) 轮共两次询问:
- 第一次询问,除了 \(a_p = 2\) 以外,其他所有 \(a_i = 1\),设程序返回的整数是 \(q_1\),则:若 \(q_1 \neq 0\) 且 \(q_1 \neq p\),说明第 \(p\) 个位置上面的数比第 \(q_1\) 个位置上面的数小 \(1\),则从 \(p\) 指向 \(q_1\) 一条边(\(p \rightarrow q_1\));
- 第二次询问,除了 \(a_p = 1\) 以外,其他所有 \(a_i = 2\),设程序返回的整数是 \(q_2\),则:若 \(q_2 \neq 0\) 且 \(q_2 \neq p\),说明第 \(p\) 个位置上面的数比第 \(q_1\) 个位置上面的数大 \(1\),则从 \(q-2\) 指向 \(p\) 一条边(\(q_2 \rightarrow p\))。
然后这 \(2n\) 轮操作之后会发现 —— 形成了一条链,根据链还原出每个数的位置即可。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
bool in[maxn];
int n, q1, q2, g[maxn], x;
void ask1(int p) {
printf("?");
for (int i = 1; i <= n; i ++) printf(" %d", (i == p) ? 2 : 1);
puts("");
fflush(stdout);
}
void ask2(int p) {
printf("?");
for (int i = 1; i <= n; i ++) printf(" %d", (i == p) ? 1 : 2);
puts("");
fflush(stdout);
}
int val[maxn];
bool vis[maxn];
int main() {
scanf("%d", &n);
for (int p = 1; p <= n; p ++) {
ask1(p);
scanf("%d", &q1);
if (q1 != 0 && q1 != p) {
g[p] = q1;
}
ask2(p);
scanf("%d", &q2);
if (q2 != 0 && q2 != p) {
g[q2] = p;
}
}
for (int i = 1; i <= n; i ++) {
if (!g[i]) x = i;
}
for (int i = 1; i <= n; i ++) vis[ g[i] ] = true;
for (int i = 1; i <= n; i ++) if (!vis[i]) { x = i; break; }
for (int i = 1; i <= n; i ++) {
val[x] = i;
x = g[x];
}
printf("!");
for (int i = 1; i <= n; i ++) printf(" %d", val[i]);
return 0;
}