CF1764G Doremy's Perfect DS Class

Easy Version & Medium Version

考虑 \(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\) 后的数组中也是成单出现的。

仍然考虑二分。

此时有两种情况:

  1. 其中一个区间的成单元素个数比另一个多 \(2\)。则 \(1\)\(n\) 都在此区间内,继续在此区间二分即可。

  2. 两区间的成单元素个数相等。则 \(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;
}

Hard Version

考虑优化掉最后一次询问。

最后一次询问的区间长度可能为 \(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;
}
posted @ 2026-04-07 19:03  TP2010  阅读(10)  评论(0)    收藏  举报