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;
}