题解:CF1562F Tubular Bells

前言

本文同步自洛谷专栏,题目传送门

*2900 的思维题,还是相当有难度的。

前置知识:随机数,基础素数内容。

分析

每次只能获取 \(\operatorname{lcm}\),可利用的信息并不多。

给出的 \(n\) 次应该是用于确定每一个位置的,考虑何种信息能让我们 \(1\) 次即可确定。

如果我们存在一个已知的数 \(p\),使得 \(\forall i\in[l,r]\land i\ne p,\operatorname{lcm}(i,p)=i\times p\),那么就容易确定。

推导有:\(\forall i\in[l,r]\land i\ne p,\gcd(i,p)=1\),则在 \(n\) 很大时,\(p\) 应该是个素数,且 \(p>\frac{r}{2}\),称满足条件的数为关键素数

小范围情况

\(n\) 很小时,可能没有这样的素数,那么我们可以用 \(\frac{n(n-1)}{2}\) 次暴力得到两两之间的 \(\operatorname{lcm}\)

然后对每一个数,对和其有关的 \(n-1\)\(\operatorname{lcm}\)\(\gcd\),即可得出该数。(有一种特殊情况见证明)

$\red{\text{证明}}$

有一个关键性质:\(\gcd(i,i+1)=1\)

据此,对于一个数 \(i\in[l,r]\)\(\gcd(\operatorname{lcm}(i,p),\operatorname{lcm}(i,p+1))=i\)

那么对于 \(n\ge 4\) 的情况,就一定存在一个 \(p\) 满足 \(p\ne i\land p+1\ne i\land p\in[l,r)\)

对于 \(n=3\) 的情况,发现只有形如 \(2i,2i+1,2i+2\) 的排列需要特殊处理,把最大值除以二即可。


这个 \(n\) 的最大值,通过本地打表可知:\(n\le 85\),暴力 \(n\le 100\) 即可满足题意。

大范围情况

如果你会 pollard-rho,可以移步至这篇文章,有更优秀的做法。

\(n\) 很大时,只能采取一点随机的方法(交互库是非自适应的),

首先得到一个 \(1\)\(n\) 的随机排列 \(A\),然后我们去尽可能多地检查 \(A_i\) 位上的数是否满足题意。

每次检查时,沿用小范围时的思路,不断选取一个不同于 \(A_i\) 的下标 \(p\),维护一个 \(now\)

查询 \(x=\operatorname{lcm}(a_p,a_{A_i})\),然后 \(now\leftarrow\gcd(now,x)\),直到 \(now\) 保持不变,如果是质数,则维护 \(\max\),否则舍弃。

上述方法正确性如何呢?

首先关键素数一定 \(>50\),那么 \(now\le 50\) 时可以直接退出(注意 \(now\) 的初始值为 \(0\),需要判断一下),

每次取 \(\gcd\),有不超过 \(\frac{1}{50}\) 的概率,\(x\)\(now\) 的倍数;否则 \(now\) 至少减半。

最后保持不变的情况,考虑连续 \(3\) 次相同时结束,那么错误率不高于 \(\frac{1}{(2\times 50)^2}=10^{-4}\),且误判不会干扰结果。

$\red{\text{关于误判不会干扰结果的说明}}$

对一个素数误判,则 \(now\) 会是一个合数,只要还有其它关键素数,就不会有影响。

对合数误判自然没有问题。


上界大概为 \(\log{A}+O(1)\) 次,且本地测试表明,此上界十分宽松,超过 \(20\) 次时退出即可。(不退出可能在 \(2\) 等小素数上浪费过多次数)

这样我们至少可以检验 \(250\) 个数,

而打表表明,\([\max(1,r-10000),r](r\ge 100)\) 中,关键素数的密度最小约为 \(0.0445610\)

正确性就是有保证的。

实现

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
mt19937 rnd(std::chrono::high_resolution_clock::now().time_since_epoch().count());
#define qry(x,y) cout<<"? "<<(x)<<' '<<(y)<<endl,cout.flush(),cin>>ret
#define N 200005
#define M 100005

int vis[N],pr[M],cnt[M];
int n,pcnt,Tcnt,Testid,E[M];
long long ret,A[M];

void subtaskA(){//不一定有质数,暴力
    for(int i=1;i<=n;i++){
        for(int j=1;j<i;j++){
            qry(i,j),A[i]=__gcd(A[i],ret);
            A[j]=__gcd(A[j],ret);
        }
    }//每个数对应地去求 gcd,得到的一定是自己
    if(n>3||A[1]%2||A[2]%2||A[3]%2) return;//n==3 的情况 2i 2i+1 2i+2 的情况会出错
    int x=A[1]>A[2]?1:2;x=A[x]>A[3]?x:3,A[x]>>=1;
}

void solve(){
    cin>>n;
    for(int i=1;i<=n;i++) A[i]=0,E[i]=i;
    if(n<=100){subtaskA();return;}
    int maxn=0,pl=0,rest=5000;shuffle(E+1,E+1+n,rnd);
    for(int i=1,p,chk,lst,tot;rest&&i<=n;i++){
        chk=0,lst=-1,tot=0,Testid++;
        long long now=0;
        for(;rest&&chk<2&&(now>50||now==0)&&tot<=20;){//保底可以找 250 个数
            //所得数 <=50 可以直接舍弃
            for(p=rnd()%n+1;cnt[p]==Testid||p==E[i];) p=rnd()%n+1;
            //每次选的数不同会更有利
            cnt[p]=Testid,qry(E[i],p),now=__gcd(now,ret);
            //Testid 是用于避免反复清空导致复杂度上升
            chk=now==lst?chk+1:0,lst=now,++tot,rest--;
        }//连续 2 次不变即判定成功确认该数
        if(chk>1&&now<N&&!vis[now]&&now>maxn) maxn=now,pl=E[i];//值得注意的是,  
    }//由于期望为找出 >r/2 的一个素数,n>100,至少为 50,小数值不会影响
    for(int i=1;i<=n;i++){
        if(i==pl) A[i]=maxn;//避免询问 (pl,pl) 出错
        else qry(i,pl),A[i]=ret/maxn;
    }
}   

void isprime(){
    for(int i=2;i<N;i++){
        if(!vis[i]) pr[++pcnt]=i;
        for(int j=1;i*pr[j]<N;j++){
            vis[i*pr[j]]=1;
            if(i%pr[j]==0) break;
        }
    }
}//在 [r-100000,r] 中 >r/2 的素数密度约为 0.0445610

int main(){
    isprime(),cin>>Tcnt;
    while(Tcnt--){
        solve(),cout<<"! ";
        for(int i=1;i<=n;i++) cout<<A[i]<<" ";
        cout<<endl,cout.flush();
    } 
    return 0;
}


posted @ 2026-06-05 17:39  Wxb2010  阅读(4)  评论(0)    收藏  举报