ARC179C 题解

Description

有一个黑板和一个长度为 \(n\) 的数组 \(A\),以及一个正数 \(R\),保证 \(|A_i|\leq R,|\sum_{i=1}^n A_i|\leq R\),但你并不知道 \(A\) 数组和 \(R\) 的具体值。初始时黑板上按顺序写着 \(X_i=A_i(1\leq i \leq n)\)

有两种操作

+ i j:擦去 \(X_i,X_j\) 并将 \(X_i+X_j\) 写到黑板上,返回 \(X_i+X_j\) 这个数是第几个被写上去的数,要求满足 \(|X_i+X_j|\leq R\)

? i j:查询 \(X_i<X_j\) 是否成立。

\(n \leq 1000\),需要在 \(25000\) 次操作内使黑板上只剩下一个数 \(\sum_{i=1}^n A_i\)


Solution

经过 \(n-1\) 次合法的 + 操作后,留下的数一定是 \(\sum_{i=1}^n A_i\),所以问题在于如何利用 ? 询问保证每一次 + 都合法。

我们不能能通过询问得到任何具体的值,只能知道两个元素的相对大小关系,考虑如何利用大小关系去保证 \(|{X_i+X_j}|\leq R\)。每一次 + 操作前的局面都形如:存在一些数 \(X_i\) 且满足所有的 \(|X_i|\leq R\)\(|\sum X_i| \leq R\)。可以发现,若每次选择最大的数和最小的数相加,绝对值一定不会超过 \(R\)

设当前最大的数为 \(mx\),最小的数为 \(mn\),口胡一下证明:

\(mx<0\),那么当前所有数都是负数,所有数的和的绝对值 等于 所有数绝对值的和,因此其中任意两数的绝对值的和都小于等于所有数和的绝对值,因此 \(\leq R\)

\(mn\leq 0 \leq mx\),那么 \(|mx+mn|\leq \max(|mx|,|mn|) \leq R\)

\(mn>0\) ,则当前所有数都是正数,和所有数都是负数的证明差不多。

下面的问题就是如何在有限的交互次数内维护最大值和最小值。交互次数限制为 \(25000\)\(n \leq 1000\),观察到 \(25000\) 大约是 \(2n \log_2 n\)

可以先把初始数组 \(A\) 排序,这里排序使用归并排序,这部分比较次数是 \(O(n \log n)\)。然后每次 + 后,二分插入得到的 \(X_i+X_j\),插入次数是 \(O(n)\),每次二分是 \(O(\log n)\),所以这部分的交互次数也是 \(O(n\log n)\),总交互次数大约是 \(2n\log_2 n\) ,可以通过本题。


Code

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define mod 998244353
#define N 2010
#define pb emplace_back
#define szi sizeof(int)
#define il inline
int n,p[N],t[N],nw,ll,rr,mm;
il int cmp(int i,int j){ //比较操作
	int x; printf("? %d %d\n",i,j),fflush(stdout);
	scanf("%d",&x); return x;
}
il int add(int i,int j){ //加法操作
	int x; printf("+ %d %d\n",i,j),fflush(stdout);
	scanf("%d",&x); return x;
}
il void rpl(){
	puts("!"),fflush(stdout); return ;
}
il void msort(int l,int r){ //我手写归并排序了,如果要写简单一点可以直接用 stable_sort,这个函数是基于归并排序实现的
	if(l==r) return ;
	int mid=l+r>>1,pl=l,pr=mid+1,tot=l-1;
	msort(l,mid),msort(mid+1,r);
	while(pl<=mid||pr<=r){
		if(pr>r){t[++tot]=p[pl++];continue;}
		if(pl>mid){t[++tot]=p[pr++];continue;}
		if(cmp(p[pl],p[pr])) t[++tot]=p[pl++];
		else t[++tot]=p[pr++];
	}
	for(int i=l;i<=r;++i) p[i]=t[i];
	return ;
}
signed main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i) p[i]=i;
	msort(1,n);
	for(int i=1;i<n;++i){
		nw=add(p[i],p[n]); ll=i+1,rr=n-1;
		while(ll<=rr){ //二分插入位置
			mm=ll+rr>>1;
			if(cmp(nw,p[mm])) rr=mm-1;
			else ll=mm+1;
		}
		for(int j=n;j>ll;--j) p[j]=p[j-1];
		p[ll]=nw;
	}
	rpl();
	return 0;
}

posted @ 2024-06-03 09:42  Wonder_Fish  阅读(27)  评论(0编辑  收藏  举报