交互

题意:有一个长度为 $n$ 的 $\texttt{01}$ 串,你每次可以询问一个字符串是否是这个 $\texttt{01}$ 串的子串,在 $2n$ 次询问之内求出这个 $\texttt{01}$ 串。

死亡回放

首先可以询问一个字符串是否是这个 $\texttt{01}$ 串的子序列,那么可以二分出 $\texttt{0}$ 的数量,然后枚举空位插 $\texttt{1}$。由于害怕被卡所以如果 $\texttt{0}$ 的数量比 $\texttt{1}$ 的数量少就将 $\texttt{01}$ 交换以减少询问次数。调了四十分钟,提交之后 WA 了。

发现是子串,汗流浃背了。那估计是固定一个位置开始延伸子串,首先问一次是不是全 $\texttt{1}$ 串,如果是则结束,否则从一个 $\texttt{0}$ 开始先向右扩展,然后再向左扩展。最坏情况会询问 $1+2 \times (n-1) + 2 = 2n + 1$ 次,因为无法向右扩展需要两次询问判定。考虑如何卡询问常数,很显然第 $2n$ 次询问如果是错的也可以确定串的最左端一位是啥,但是处理起来会有点麻烦。而每次随机问 $\texttt{01}$ 有 $\dfrac{1}{2}$ 的概率直接问对,那么被卡到最坏询问次数的概率就是 $\dfrac{1}{2^{100}}$,太对了哥。

实际上被卡到最坏询问次数的概率是 $\dfrac{1}{2^{n}}$,太错了,最后我化成雪白的灰。

只需要在第 $2n$ 次询问后直接输出答案就可以做到最坏 $2n$ 次询问,时间复杂度 $O(n)$。

#include<bits/stdc++.h>
using namespace std;
int n,cnt;
int ask(string s){
    static int ret;
    cout<<'?'<<' '<<s<<endl;
    cin>>ret,++cnt;
    return ret;
}
void answer(string s){
    cout<<'!'<<' '<<s<<endl;
}
void solution(){
    cin>>n,cnt=0;
    string s;
    s.resize(n,'1');
    if(ask(s)) return answer(s);
    s="0";
    while(s.size()<n){
        if(ask(s+'0')) s=s+'0';
        else if(cnt==n*2||ask(s+'1')) s=s+'1';
        else break;
    }
    while(s.size()<n){
        if(ask('0'+s)) s='0'+s;
        else if(cnt==n*2||ask('1'+s)) s='1'+s;
        else break;
    }
    answer(s);
}
int T;
int main(){
    ios::sync_with_stdio(0);
    cin>>T;
    while(T--) solution();
    return 0;
}
最后修改:2024 年 09 月 26 日
如果觉得我的文章对你有用,请随意赞赏