[CF1526F]Median Queries
[CF1526F]Median Queries
壹、题目描述 ¶
现在有一个长度为 \(n\) 的排列 \(p\),其中的数字顺序被打乱了,但是你知道 \(p_1<p_2\).
你想知道这个排列长什么样子,但是你只能使用以下这一种询问不超过 \(2n+420\) 次:
? a b c
,回答你的是 \(\{|p_a-p_b|, |p_b-p_c|, |p_c-p_a|\}\) 的中位数;
当你得到排列后,使用以下格式回答:
! p1 p2 p3 ... pn
;
收到回答之后,程序会返回 \(-1\)(完全错误)或者 \(1\)(完全正确),请一定要读入他们.
一共有 \(T(T\le 1000)\) 组测试,保证 \(\sum n\le 10^5\).
在每组数据开始前,会先读入一个 \(n\),表示排列长度,然后开始交互过程。
贰、题解 ¶
提示一:找到 \(1\) 和 \(2\)(或者 \(n-1\) 和 \(n\))就能找到全部!那么我们能不能用 \(n+420+2\) 次询问找到 \(1\) 和 \(2\) 呢?
提示二:想要找到 \(1\) 或 \(n\),那么我们能否找到一对数 \((a,b)\) 使得 ? 1 a b
或者 ? n a b
有点特别(其中 \(1\) 指的是 \(1\) 这个数字对应的下标,\(n\) 同理)?更明了的,尝试找到 \(|p_a-p_b|\le {n\over 3}-c\)(\(c\) 是一个常数)。
本题关键者三:
- 找到一对数 \((a,b)\) 使得他们的长度不超过数组长度的 \(1\over 3\);
- 找到 \(1,2\)(或者 \(n-1,n\));
- 找到剩下的数字;
考察询问的含义:返回三个点之间,两两距离的中位数 —— 排除掉最大值,就是三个点中每相邻两个点的距离的最大值,即对于 \(p_a<p_b<p_c\),返回 \(\max\{p_b-p_a,p_c-p_b\}\).
即如提示所言,当 \(|p_a-p_b|\le {n\over 3}-c\) 时,询问到某个 \(p_x=1\or p_x=n\) 时,一定有 \(\text{query}(a,b,x)\) 的返回值是所有询问中最大的。但是如何找到 \((a,b)\)?
想找 \((a,b)\),只需要找到一个三元组 \((a,b,c)\),保证 \(\text{query}(a,b,c)\le \floor{n-4}{6}\),因为找到这样的三元组之后,即使 \(p_a,p_b\) 是相隔最远的(分居 \(c\) 的两侧),也一定有 \(|p_a-p_b|\le 2\floor{n-4}{6}=\floor{n-4}{3}\).
又如何寻找 \((a,b,c)\)?我们只需要任意选择 \(13\) 个点,在这 \(13\) 个点所形成的三元组中,一定会有一组满足这个条件,暴力询问的次数也只有 \({13\choose 3}=286\) 种,完全足够。
至于为什么只需要 \(13\) 点?考虑以下证明:
Lemma#1 Proof
使用反证法进行证明。如果存在有 \(13\) 个点都无法满足条件,我们可以进行贪心地构造:
先放一个点在 \(1\),再放一个点前一个点挨在一起,再放一个点和前一个点距离 \(\floor{n-4}{6}+1\),再放一个点挨在一起,再放一个点距离 \(\floor{n-4}{6}+1\)......
那么最后,这个数列的长度即为
\[\left(\left\lceil{13\over 2}\right\rceil-1\right)\times \left(\floor{n-4}{6}+1\right)+13 =6\left(\floor{n-4}{6}+1\right)+13\ge 6\left({n-4-5\over 6}+1\right)+13=n+10>n \]所以这样的数列不存在。
现在,我们找到了 \((a,b,c)\),该如何寻找 \(1\) 或者 \(n\)?显然,我们只需要将 \(x\neq a\and x\neq b\) 的 \(x\) 全部询问一遍,得到所有的询问返回值,其中,返回值最大的对应的 \(x\) 的 \(p_x=1\or p_x=n\). 这比较显然,因为根据我们对于询问的定义,只有当问到最边界时,距离才会是最大值。并且,得到最大值的 \(x\) 甚至可能有两个 —— \(1,n\) 同时可以取到最大值,但是我们可以只考虑其中一种情形:
我们假定得到的是 \(p_x=1\),那么这里只有可能有两个 \(y\) —— \(y_1,y_2\) 可能都满足 \(p_y=2\),因为 \(\text{query}(a,b,1)\) 为最大,那么同理,\(\text{query}(a,b,2)\) 定是次大,但是这样的次大值也有可能有俩 —— \(2,n-1\or n\) 同时取次大,这就和 \(1,n\) 同时取最大一样,但是我们可以比较出谁对应的 \(p\) 是 \(2\),因为我们一定有 \(\text{query}(1,y_1,a)\le \text{query}(1,y_2,a)(p_{y_1}=2)\).
为什么 \(\text{query}(1,y_1,a)\le \text{query}(1,y_2,a)(p_{y_1}=2)\)?
Lemma#2 Proof
当 \(p_{y_1}=2\) 时,我们的询问 \(\text{query}(x,y_1,a)=\max\{|1-2|,|2-a|\}=a-2\);
而对于 \(\text{query}(1,y_2,a)\),它会是 \(\max\{|1-(n-1)|,|(n-1)-a|\}=n-2\or \max\{|1-n|,|n-a|\}=n-1\),显然,\(a\le n\),所以无论如何,都会有 \(a-2\le n-2\).
得到 \(1,2\) 之后,显然,对于每次询问 \(\text{query}(x,y,i)=p_i-2\),再用 \(n-2\) 次询问就搞定了。
代码实现的时候,并没有严格区分我们最开始找到的是 \(1\) 还是 \(n\),我们先假设它为 \(1\),但如果它是 \(n\),那么肯定不满足我们已知的 \(p_1<p_2\),考虑在这种情形下,我们找到的所有数字都是 \(n-1-x\),只需要赋值 \(x\leftarrow n-x+1\) 就可以得到真正的排列了。
叁、参考代码 ¶
#include<cstdio>
#include<string>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<map>
using namespace std;
// #define NDEBUG
#include<cassert>
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define siz(a) ((a).size())
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
const int maxn=100000;
inline int query(int a, int b, int c){
printf("? %d %d %d\n", a, b, c);
Endl; fflush(stdout);
return readin(1);
}
int ans[maxn+5], n;
map< int, vector<int>, greater<int> >m;
vector<int>tup;
inline void find_tuple(){
tup.clear();
rep(i, 1, 13) rep(j, i+1, 13) rep(k, j+1, 13){
if(query(i, j, k)<=(n-4)/6){
tup={i, j, k}; return;
}
}
}
inline void print_ans(){
putchar('!');
rep(i, 1, n) printf(" %d", ans[i]);
Endl; fflush(stdout);
readin(1);
}
signed main(){
rep(_, 1, readin(1)){
n=readin(1);
m.clear(); tup.clear();
find_tuple();
assert(siz(tup)==3);
int a=tup[0], b=tup[1];
rep(i, 1, n){
if(i!=a && i!=b){
m[query(a, b, i)].push_back(i);
}
}
int mx=m.begin()->fi;
if(siz(m[mx-1])>=2){
assert(siz(m[mx-1])==2);
if(query(m[mx][0], m[mx-1][0], a)>query(m[mx][0], m[mx-1][1], a))
swap(m[mx-1][0], m[mx-1][1]);
}
int x=m[mx][0], y=m[mx-1][0];
ans[x]=1, ans[y]=2;
rep(i, 1, n) if(i!=x && i!=y)
ans[i]=query(i, x, y)+2;
if(ans[1]>ans[2]) rep(i, 1, n) ans[i]=n-ans[i]+1;
print_ans();
}
return 0;
}