[CF1526F]Median Queries

\[\newcommand\floor[2]{\genfrac{\lfloor}{\rfloor}{1pt}{}{#1}{#2}} \]

[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;
}
posted @ 2021-05-30 12:24  Arextre  阅读(123)  评论(1编辑  收藏  举报