Solution
我们不能直接查询数列前几项,只能查询 \(L\le i<M\) 的位置。这意味着很可能需要利用数列本身的数学结构和性质。
我们需要一个可以让我们从任意靠后的几项推出 \(p,q\) 的性质。
这里直接上结论
\f_i\^2-f_{i-1}f_{i+1}\\equiv(-q)\^i \\
不难归纳证明。(专门对着这个式子出的题吧,没学过二阶齐次线性递推做不出来很正常)
最自然的想法是问 \(f_{M-3},f_{M-2},f_{M-1}\)。
此时根据结论,有
\f_{M-2}\^2-f_{M-3}f_{M-1}\\equiv(-q)\^{M-2} \\
由费马小定理,\((-q)^{M-2}\equiv(-q)^{-1}\),因此
\q\\equiv-\\left(f_{M-2}\^2-f_{M-3}f_{M-1}\\right)\^{-1} \\
我们就得到了 \(q\)。再来考虑如何得到 \(p\)。
由递推式得
\\\begin{aligned} f_{M-1}\&\\equiv qf_{M-3}+pf_{M-2} \\\\ pf_{M-2}\&\\equiv f_{M-1}-qf_{M-3} \\end{aligned} \\
此时如果 \(f_{M-2}\not\equiv 0\) 我们就做完了。接下来只需要考虑 \(f_{M-2} \equiv 0\) 的情况。
此时仅仅知道 \(f_{M-3},f_{M-2},f_{M-1}\) 肯定得不出 \(p\),尝试查询 \(f_{M-4}\)。
由结论得
\f_{M-3}\^2-f_{M-4}f_{M-2} \\equiv(-q)\^{M-3} \\
由费马小定理和 \(f_{M-2}\equiv 0\) 得
\f_{M-3}\^2\\equiv(-q)\^{-2} \\
因此 \(q\equiv\pm \frac{1}{f_{M-3}}\)。
显然 \(f_{M-3}\not\equiv 0\)(不然整个数列就全 \(0\) 了)。可以放心将解出的两个 \(q\) 代入 \(f_{M-2}\equiv pf_{M-3}+qf_{M-4}\) 解出两个 \(p\),分别提交即可。
最终交互策略:
| 步骤 | 操作 |
|---|---|
| 第一次 | 查询 \(f_{M-2}\) |
| 第二次 | 查询 \(f_{M-3}\) |
| 第三次,若 \(f_{M-2}\not\equiv0\) | 查询 \(f_{M-1}\),求出唯一解 |
| 第三次,若 \(f_{M-2}\equiv0\) | 查询 \(f_{M-4}\),求出两组解 |
Code
cpp
#include <iostream>
#define int long long
using namespace std;
int m,l;
int query(int x){
int res;
cout<<"? "<<x<<endl;
cin>>res;
return res;
}
void guess(int p1,int q1,int p2,int q2){
p1=(p1+m)%m,p2=(p2+m)%m;
q1=(q1+m)%m,q2=(q2+m)%m;
cout<<"! "<<p1<<' '<<q1<<' '<<p2<<' '<<q2<<endl;
}
int ksm(int x,int y){
int res=1;
while(y){
if(y&1) (res*=x)%=m;
(x*=x)%=m,y>>=1;
}
return res;
}
void solve(){
cin>>m>>l;
// f_{m-4}:a
// f_{m-3}:b
// f_{m-2}:c
// f_{m-1}:d
int c=query(m-2),b=query(m-3);
if(c){
int d=query(m-1);
int q=-ksm((c*c%m-b*d%m)%m,m-2);
int p=(d-q*b)%m*ksm(c,m-2)%m;
guess(p,q,p,q);
}else{
int a=query(m-4);
int q=ksm(b,m-2);
int p=-q*a%m*ksm(b,m-2)%m;
guess(p,q,-p,-q);
}
}
signed main(){
int T;
cin>>T;
while(T--) solve();
return 0;
}