CF1764G Doremy's Perfect DS Class
考虑 \(1\) 的特殊性:对于任意 \(k \ge 2\),\(\lfloor\frac{1}{k}\rfloor=0\)。
更特殊地,对于 \(k=2\),如果 \(n\) 为奇数,则只有 \(1\) 的结果为 \(0\),且在除以 \(2\) 后的数组中,除了 \(0\) 以外其他数字都是成对出现。
若 n 为奇数
考虑二分。
所以,二分出 \(mid\) 后,记 \([1,mid]\) 为左区间,\([mid+1,n]\) 为右区间。
包含 \(1\) 的区间的成单元素要比另一个区间多 \(1\)。我们容易通过 \(1\) 次询问得到一个区间的成单元素个数。左右区间分别要问一次。
这样就可以通过二分来在 \(2\log n=20\) 次内得到 \(1\) 的位置。足以通过 Hard Version。
若 n 为偶数
此时,\(\frac{n}{2}\) 在除以 \(2\) 后的数组中也是成单出现的。
仍然考虑二分。
此时有两种情况:
-
其中一个区间的成单元素个数比另一个多 \(2\)。则 \(1\) 和 \(n\) 都在此区间内,继续在此区间二分即可。
-
两区间的成单元素个数相等。则 \(1\) 和 \(n\) 在不同的区间内。此时只要对其中较长的区间问一下 \(k=n\) 即可,以为只有 \(\frac{n}{n}=1\)。这样就可以区分 \(1\) 和 \(n\),每次把包含 \(n\) 的区间成单元素数量减去 \(1\)(即 \(n\) 的贡献),然后就回到了 \(n\) 为奇数的二分方式。
次数为 \(2\log n+1=21\),可以通过 Easy & Medium Version。
#include <bits/stdc++.h>
using namespace std;
int n;
inline int ask(int l,int r,int k){
if (l==r) return 1;
cout<<"? "<<l<<" "<<r<<" "<<k<<endl;
int res;cin>>res;return res;
}
int main(){
cin>>n;
int l=1,r=n,d1=0,d2=0;
while (l<r){
int mid=l+r>>1;
int cl=ask(1,mid,2)*2-mid-d1;
int cr=ask(mid+1,n,2)*2-(n-mid)-d2;
if (cl>cr) r=mid;
else if (cl<cr) l=mid+1;
else {
if (mid>n-mid) {
if (ask(1,mid,n)==2) l=mid+1,d1=1;
else r=mid,d2=1;
}
else {
if (ask(mid+1,n,n)==2) r=mid,d2=1;
else l=mid+1,d1=1;
}
}
}
cout<<"! "<<l;
}
考虑优化掉最后一次询问。
最后一次询问的区间长度可能为 \(2\) 或 \(3\)。
如果是 \(3\),那么会被二分为长度分别为 \(1\) 和 \(2\) 的区间,且要走到 \(1\),那么说明还不是最劣情况,因为走到 \(2\) 还要多一次,那么此时的次数本来就小于 \(21\),不做其他优化。
那么,要考虑的最后的区间 \([l,r]\) 长度为 \(2\)。
记 \([l,r]\) 内是 \(1,x\) 两个元素,且假设 \(x\not =n\)。
根据前面的过程,此时 \([1,l-1]\),\([1,r]\),\([r+1,n]\) 会被查询过(当然,如果区间不合法结果就是 \(0\))。
那么,\([1,r]\) 的成单元素个数与 \([1,l-1]\) 的相等,说明 \(x\) 在 \([1,l-1]\) 中有可以匹配的元素,即 \([l,r]\) 在 \([r+1,n]\) 中没有匹配的,则 \([r,n]\) 的成单元素一定比 \([r+1,n]\) 多一,就省去了一次查询。
同理,如果 \([1,r]\) 的成单元素与 \([1,l-1]\) 的多 \(2\),说明 \(x\) 在 \([r+1,n]\) 中有可以匹配的元素,则 \([1,l]\) 的成单元素一定比 \([1,l-1]\) 多一。
如果 \(x=n\),就会被认为是后一种情况,也省掉了一次查询。
这样就可以做到 \(2\log n\) 次。
时间复杂度 \(O(\log^2 n)\)。
#include <bits/stdc++.h>
using namespace std;
int n;
map <pair<int,int>,int> mp;
inline int ask(int l,int r,int k){
if (l==r) return 1;
cout<<"? "<<l<<" "<<r<<" "<<k<<endl;
int res;cin>>res;return res;
}
int main(){
cin>>n;
mp[{1,0}]=mp[{n+1,n}]=0,mp[{1,n}]=1+(n%2==0);
int l=1,r=n,d1=0,d2=0;
while (l<r){
int mid=l+r>>1,cl=-1,cr=-1;
if (l==r-1){
if (mp[{1,r}]==mp[{1,l-1}]) cr=mp[{r+1,n}]+1-d2;
if (mp[{1,r}]==mp[{1,l-1}]+2) cl=mp[{1,l-1}]+1-d1;
}
if (cl==-1) cl=ask(1,mid,2)*2-mid-d1;mp[{1,mid}]=cl+d1;
if (cr==-1) cr=ask(mid+1,n,2)*2-(n-mid)-d2;mp[{mid+1,n}]=cr+d2;
if (cl>cr) r=mid;
else if (cl<cr) l=mid+1;
else {
if (mid>n-mid) {
if (ask(1,mid,n)==2) l=mid+1,d1=1;
else r=mid,d2=1;
}
else {
if (ask(mid+1,n,n)==2) r=mid,d2=1;
else l=mid+1,d1=1;
}
}
}
cout<<"! "<<l;
}

浙公网安备 33010602011771号