[CF1526F] Median Queries(交互 / 构造)

题面

这是一道交互题。

有一个未知的长度为 N \tt N N 的排列 P \tt P P,已知 P 1 < P 2 \tt P_1 < P_2 P1<P2

每次询问格式为 “ ?   a   b   c \tt?~a~b~c ? a b c ”,返回值为三元组 { ∣ P a − P b ∣ , ∣ P b − P c ∣ , ∣ P a − P c ∣ } \tt \{|P_a-P_b|,|P_b-P_c|,|P_a-P_c|\} {PaPb,PbPc,PaPc} 的中位数。

在至多 2 n + 420 \tt 2n+420 2n+420 次询问后,输出排列 P \tt P P,格式为 “ !   P 1   P 2   …   P N \tt!~P_1~P_2~\dots~P_N ! P1 P2  PN ”。

然后每组数据会返回一个数,为 1 \tt1 1 则答案正确,为 − 1 \tt-1 1 则错误,不要忘了输入这个数。

一共 t ( 1 ≤ t ≤ 1000 ) \tt t(1\leq t\leq1000) t(1t1000) 组数据,保证 20 ≤ N ≤ 1 0 5 , ∑ N ≤ 1 0 5 20\leq N\leq 10^5,\tt \sum N\leq 10^5 20N105,N105

题解

有两种各占优势的做法。


Solution #1

首先,返回该三元组的中位数,相当于:若 P a > P b > P c \tt P_a>P_b>P_c Pa>Pb>Pc,则返回 max ⁡ { P a − P b , P b − P c } \tt\max\{P_a-P_b,P_b-P_c\} max{PaPb,PbPc}

有了这个简化,才能更好地继续推。

该解法的核心在于:如果已经知道了 1 \tt1 1 2 \tt2 2 的位置(假设为 P A , P B \tt P_A,P_B PA,PB),那么可以在 n − 2 \tt n-2 n2 次操作后,确定整个排列。也就是分别询问 { A , B , i } \tt\{A,B,i\} {A,B,i},所得值( q u e r y ( A , B , i ) \tt query(A,B,i) query(A,B,i) + 2 \tt+2 +2 便是 P i \tt P_i Pi 了。

问题是怎么获得 1 \tt1 1 2 \tt2 2 的位置,或者说,也可以获得 N \tt N N N − 1 \tt N-1 N1 的位置,总之通过 P 1 < P 2 \tt P_1<P_2 P1<P2 确定最终答案。

经过一段尝试,我们发现,如果找到这么两个位置 a \tt a a b \tt b b ,满足 ∣ P a − P b ∣ ≤ N − 4 3 \tt|P_a-P_b|\leq \frac{N-4}{3} PaPb3N4 ,处理出所有的 q [ i ] = q u e r y ( a , b , i ) \tt q[i]=query(a,b,i) q[i]=query(a,b,i) ,那么 q [ i ] \tt q[i] q[i] 最大的 i \tt i i 就是 1 \tt1 1 或者 N \tt N N 的位置, q [ i ] \tt q[i] q[i] 严格次大的 i \tt i i 就是 2 \tt2 2 或者 N − 1 \tt N-1 N1 的位置。这样只需要多进行 N − 2 \tt N-2 N2 次操作。

那么,在剩下的 424 \tt424 424 次操作内,我们需要解决两个问题:

  • 找到这样的位置 a , b \tt a,b a,b
  • 找到 1 \tt1 1 2 \tt2 2 的或者 N \tt N N N − 1 \tt N-1 N1 的位置 A , B \tt A,B A,B

找 a b:

我们可以证明:对于任意 13 \tt13 13 个位置,总存在 3 \tt3 3 个位置 x , y , z \tt x,y,z x,y,z ,满足 q u e r y ( x , y , z ) ≤ n − 4 6 \tt query(x,y,z)\leq\frac{n-4}{6} query(x,y,z)6n4 ⇒ max ⁡ { ∣ P x − P y ∣ , ∣ P y − P z ∣ , ∣ P x − P z ∣ } ≤ n − 4 3 \tt\Rightarrow \max\{|P_x-P_y|,|P_y-P_z|,|P_x-P_z|\}\leq\frac{n-4}{3} max{PxPy,PyPz,PxPz}3n4)。

可以用反证法进行证明,即假设存在某 13 \tt13 13 个位置,满足 ∀ { x , y , z } , max ⁡ { ∣ P x − P y ∣ , ∣ P y − P z ∣ , ∣ P x − P z ∣ } > n − 4 3 \tt\forall \{x,y,z\},\max\{|P_x-P_y|,|P_y-P_z|,|P_x-P_z|\}>\frac{n-4}{3} {x,y,z},max{PxPy,PyPz,PxPz}>3n4 。贪心地填数进去,你就会发现,刚好到 13 \tt13 13 个时,你就满足不了条件了。所以这种情况不存在,假设不成立。

找这 3 \tt3 3 个位置,需要枚举 ( 13 3 ) = 286 \tt{13\choose3}=286 (313)=286 种情况,完全足够。

找到这样的 3 \tt3 3 个位置时,任选两个就可以作 a , b \tt a,b a,b 了。

找 A B:

我们会发现,假如你确定了某个 q [ i ] \tt q[i] q[i] 最大的 A \tt A A 作为 1 \tt1 1 的位置,那么还是有两个位置可能为 2 \tt2 2 ,一个是真 2 \tt2 2 ,一个是 N − 1 \tt N-1 N1 ,令其分别为 B 1 , B 2 \tt B_1,B_2 B1,B2 ,我们稍稍归纳一下就会发现:
q u e r y ( A , B 1 , a ) ≤ q u e r y ( A , B 2 , a ) q u e r y ( A , B 1 , b ) ≤ q u e r y ( A , B 2 , b ) {\tt query(A,B_1,a)\leq query(A,B_2,a)}\\ {\tt query(A,B_1,b)\leq query(A,B_2,b)} query(A,B1,a)query(A,B2,a)query(A,B1,b)query(A,B2,b)

而且最多只有一个取等。因此便可以把 B 1 \tt B_1 B1 判断出来了。

CODE

#include<cstdio>
#include<vector>
#include<cmath>
#include<ctime>
#include<queue>
#include<map>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) ((-x) & (x))
#define INF 0x3f3f3f3f
LL read() {
	LL f=1,x=0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
int n,m,i,j,s,o,k;
inline int ask(int a,int b,int c) {
	cout<<"? "<<a<<" "<<b<<" "<<c<<endl;
	int as; cin>>as; return as;
}
int a[MAXN],p[MAXN],ar[MAXN];
vector<int> bu[MAXN];
int main() {
	srand(time(0));
	int T;cin>>T;
	while(T --) {
		cin>>n;
		for(int i = 1;i <= n;i ++) bu[i].clear(),a[i] = 0,ar[i] = i;
		random_shuffle(ar + 1,ar + 1 + n);
		int A = 1,B = 2,C = 3,as,flag1 = 0;
		for(A = 1;A <= 11;A ++) {
			for(B = A+1;B <= 12;B ++) {
				for(C = B+1;C <= 13;C ++) {
					if((as=ask(A,B,C)) <= ((n-4)/6)) {
						flag1 = 1; break;
					}
				}
				if(flag1) break;
			}
			if(flag1) break;
		}
		int mi = n+1,ct = 1,ct2 = 0,ma = 0;
		for(int i = 1;i <= n;i ++) {
			if(i != A && i != B) {
				a[i] = ask(A,B,i);
				if(a[i] < mi) mi = a[i],ct = 1;
				else if(a[i] == mi) ct ++;
				if(a[i] == 2) ct2 ++;
				ma = max(ma,a[i]);
				bu[a[i]].push_back(i);
			}
		}
		int H = bu[ma][0],H2 = bu[ma-1][0];
		if((int)bu[ma-1].size() > 1) {
			int H3 = bu[ma-1][1];
			if(ask(H,H2,A) <= ask(H,H3,A) && ask(H,H2,B) <= ask(H,H3,B)) {
				H2 = H2;
			}
			else H2 = H3;
		}
		p[H] = 1; p[H2] = 2;
		for(int i = 1;i <= n;i ++) {
			if(i != H && i != H2) {
				p[i] = 2 + ask(H,H2,i);
			}
		}
		if(p[1] > p[2]) {
			for(int i = 1;i <= n;i ++) p[i] = n-p[i]+1;
		}
		cout<<"!";
		for(int i = 1;i <= n;i ++) cout<<" "<<p[i];
		cout<<endl;
		int AC; cin>>AC;
	}
	return 0;
}

Solution #2

非常精妙的做法,不愧是 O n e I n D a r k \tt OneInDark OneInDark

主要是选择两对数来进行序列 q \tt q q 的确定,通过 q \tt q q q ′ \tt q' q 确定原序列。这里笔者就不展开了。原博客还是写了满大页的。

posted @ 2021-06-15 22:47  DD_XYX  阅读(27)  评论(0编辑  收藏  举报