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