CF1762D GCD Queries
题意
交互题,多组测试数据。
给定一个隐藏的长为 \(n\) 的 \(0\) ~ \(n-1\) 排列 \(p\),每次询问可以考虑一下两种操作之一:
-
? x y:询问该排列第 \(x\) 个数和第 \(y\) 个数的最大公约数,即 \(gcd(p_x,p_y)\); -
! x y:回答该排列第 \(x\) 个数和第 \(y\) 个数之中有一个为 \(0\);
规定 \(gcd(0,x) = gcd(x,0) = 0\),询问次数不得超过 \(2*n\) 次。
解法
说实话挺巧妙的,一道思维好题。
没有思考过的读者可以试着动动脑筋,蛮有意思的。
由于题目限制询问次数为 \(2*n\),可以合理猜想每两次询问就能得到什么信息;而又因为需要回答有关 \(0\) 的位置,猜到每两次询问可以确定一位不是零(很显然因为如果确定一位是零就没什么可做的了)。
又注意到最终的回答为两个数中的一个为 \(0\),联想到询问的策略在每次一起询问的集合大小小于等于二时就无效了。
那么考虑每次拎出还没有被判定的三个数 \(p_i\)、\(p_j\) 和 \(p_k\),观察他们两两 \(gcd\) 与零的位置的关系,可以发现:
-
若 \(gcd(p_i,p_j) = gcd(p_i,p_k)\),则 \(p_i\) 一定不为零。因为序列 \(p\) 是一个排列,而题目规定任何数与 \(0\) 的 \(gcd\) 为其本身,所以在 \(p_i\) 为零的情况下不可能出现两个数与其 \(gcd\) 的值相同。
-
若 \(gcd(p_i,p_j) > gcd(p_i,p_k)\),则 \(p_k\) 一定不为零。可以假设如果 \(p_k\) 为零,那么很显然有 \(gcd(p_i,p_k)\) 值为 \(p_i\);然而由于 \(p_k\) 已经等于零,\(p_j\) 就不可能等于零,那么 \(p_i\) 与一个不为零的数的 \(gcd\) 很显然小于其本身。
-
若 \(gcd(p_i,p_j) < gcd(p_i,p_k)\),则 \(p_j\) 一定不为零。与上一条是相似的,请读者自己思考。
根据这三个性质,就可以询问次数 \(2*(n-2)\) 次处理问题了。考虑维护三个指针,每次判定完一个数之后右移,则时间复杂度为 \(O(n)\)。\(n \leq 20000\),且询问组数 \(t \leq 10000\),总时间复杂度 \(O(t*n)\),可以通过本题。
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int tag[20005];
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t;
cin>>t;
while(t--){
int n,i=1,j=2,k=3,cnt=0;
cin>>n;
if(n==2){
cout<<"! 1 2"<<endl;
int flag;
cin>>flag;
if(flag==-1) return 0;
continue;
}
for(int i=1;i<=n;i++) tag[i]=0;
while(cnt!=n-2){
for(;i<=n;i++)
if(tag[i]==0) break;
for(;j<=n;j++)
if(tag[j]==0&&i!=j) break;
for(;k<=n;k++)
if(tag[k]==0&&i!=k&&j!=k) break;
int gcd1,gcd2;
cout<<"? "<<i<<" "<<j<<endl;
cin>>gcd1;
cout<<"? "<<i<<" "<<k<<endl;
cin>>gcd2;
if(gcd1==gcd2) tag[i]=1;
else if(gcd1>gcd2) tag[k]=1;
else tag[j]=1;
cnt++;
}
int ans1,ans2;
for(ans1=1;ans1<=n;ans1++)
if(tag[ans1]==0) break;
for(ans2=ans1+1;ans2<=n;ans2++)
if(tag[ans2]==0) break;
cout<<"! "<<ans1<<" "<<ans2<<endl;
int flag;
cin>>flag;
if(flag==-1) return 0;
}
return 0;
}
码风过丑,勿喷。

浙公网安备 33010602011771号