CF1924F 超级神秘困难势能分析题目

感觉好厉害的题。感觉好厉害的题解。

题意简述

长为 \(n\) 的数列,有一个位置是 \(1\),其他都是 \(0\)。每次可以问交互库区间 \([l,r]\) 内有没有 \(1\),交互库有可能说谎,但不会连续三次说谎,也不会连续三次说实话。最后给出最多两个位置,使得 \(1\) 的位置在其中之一。

交互次数不多于 \(\left\lceil \log_{1.116}{n} \right\rceil\)

题目分析

首先因为交互库自适应,我们是不能唯一确定 \(1\) 的位置的。交互库完全可以在奇数次回答将一个 \(i\) 作为答案,在偶数次回答将一个 \(j\) 作为答案。这样我们是看不出来的。

直接判断它是否说谎十分困难。我们考虑根本不这么做。

先转化题意。考虑维护集合 \(S\),为所有可能成为 \(1\) 的位置的集合。对每个位置维护一个栈。每次问一个区间 \([l,r]\),设答案为 \(t\),就向 \([l,r]\) 内的所有栈中 \(\mathtt{push}\) 进一个 \(t\),其他位置 \(\mathtt{push}\) 进一个 \(1-t\)。当一个位置栈顶又连续三个 \(0\)\(1\),那么他就似了。最后让 \(\left| S \right| \le 2\) 即可。

直接这样排除,我们不知道问那个区间。考虑搞一些既抽象又能让问题更具象的东西——势能。

每个位置的状态显然只和栈顶最后两个位置有关。设似了的位置势能为 \(0\),结尾为 \(\mathtt{00}\)\(\mathtt{11}\) 的位置为 \(1\),结尾为 \(\mathtt{10}\)\(\mathtt{01}\) 的位置为 \(x\)

至于为什么是 \(x\),是为了方便后面调参。

设初始势能为 \(E\)(所有位置势能之和),那么填了 \(0\)\(1\) 后的各状态势能变化如下(设填 \(0\) 势能变 \(E_0\),填 \(1\)\(E_1\)):

\(\mathtt{00}\) \(\mathtt{11}\) \(\mathtt{10}\) \(\mathtt{01}\)
初始 \(1\) \(1\) \(x\) \(x\)
\(\mathtt{0}\) \(0\) \(x\) \(1\) \(x\)
\(\mathtt{1}\) \(x\) \(0\) \(x\) \(1\)
变化(\(\dfrac{E_0 + E_1}{E}\) \(x\) \(x\) \(\dfrac{x+1}{x}\) \(\dfrac{x+1}{x}\)

最终状态势能是 \(O(1)\) 的,整个过程一定是让势能不断减小的。交互库为了阻碍我们,一定会选势能最大的结果返回给我们。因此我们要让不同结果间的差距尽可能小,也就是让 \(x = \dfrac{x+1}{x}\)。解得 \(x = \dfrac{1 + \sqrt{5}}{2}\)

这时我们知道 \(xE = E_0 + E_1\),即 \(\max(E_1,E_0) \ge \dfrac{xE}{2}\)。这揭示了如果我们选择的方案能让 \(E_0\)\(E_1\) 只差 \(O(1)\) 的话,最小操作次数就是 \(\log_{\frac{x}{2}}{n} + O(1)\) 的(答案也不能低于这个数),比要求的操作次数还要牛一些。

下面给出方案构造:

\(E_{i,0/1}\) 为询问 \([1,i]\) 这个前缀时的 \(E_{0/1}\),我们又知道 \(E_{0,0} = E_{n,1}\)\(E_{0,1} = E_{n,0}\)\(E_{0,0} - E_{0,1} = - (E_{n,0} - E_{n,1})\),每两个前缀之间 \(E\) 的变化是 \(O(1)\) 的,根据介值定理,一定存在一个前缀 \([1,i]\),使得 \(E_{i,0} - E_{i,1}\)\(O(1)\) 的。每次计算出这样的前缀直接询问即可。

前两次随便问即可。

#include<bits/stdc++.h>
#define pii std::pair<int,int>
#define mkp std::make_pair
#define fir first
#define sec second
typedef long long ll;
inline void rd(){}
template<typename T,typename ...U>
inline void rd(T &x,U &...args){
    int ch=getchar();
    T f=1;x=0;
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x*=f;rd(args...);
}
constexpr double phi=std::numbers::phi_v<double>;
const int N=1e5+5;
int T,n,a[N],alv[N];
inline int Ask(int l,int r){
    printf("? %d %d\n",l,r);
    fflush(stdout);
    int x;rd(x);
    return (x==(r-l+1));
}
constexpr double tr[4][2]={
    {0,phi},{phi,1},
    {1,phi},{phi,0}
};
inline void Solve(){
    rd(n);
    int t1=Ask(1,n),t2=Ask(1,n/2);
    for(int i=1;i<=n;i++){
        if(i<=n/2)a[i]=t1*2+t2;
        else a[i]=t1*2+(!t2);
        alv[i]=1;
    }
    int sz=n;
    while(sz>2){
        double E0=0,E1=0,mn=1e9;int pos=0;
        for(int i=1;i<=n;i++){
            if(!alv[i])continue;
            E0+=tr[a[i]][1];
            E1+=tr[a[i]][0];
        }
        for(int i=1;i<=n;i++){
            if(!alv[i])continue;
            E0+=tr[a[i]][0]-tr[a[i]][1];
            E1+=tr[a[i]][1]-tr[a[i]][0];
            if(std::fabs(E0-E1)<=mn)mn=std::fabs(E0-E1),pos=i;
        }
        int x=Ask(1,pos);
        for(int i=1;i<=n;i++){
            if(!alv[i])continue;
            int t=(i<=pos?x:(!x));
            if((a[i]==3&&t)||(a[i]==0&&(!t))){
                alv[i]=0;--sz;
                continue;
            }
            a[i]=((a[i]-(a[i]>>1<<1))<<1)+t;
        }
    }
    for(int i=1;i<=n;i++)if(alv[i]){
        printf("! %d\n",i);
        fflush(stdout);
        int x;rd(x);
    }
    fflush(stdout);
    puts("#");
    fflush(stdout);
}
signed main(){
    rd(T);
    while(T--)Solve();
    return 0;
}
posted @ 2025-02-08 11:46  KIreteria  阅读(6)  评论(0编辑  收藏  举报