方針はあっていたがバグがとり切れなかった。
https://yukicoder.me/problems/no/551
問題
奇素数Pと原始根Rが与えられる。
このPに関する2次方程式がQ個与えられるのでそれぞれ解を求めよ。
解法
通常の二次方程式を思い出し、平方完成してみよう。
まず全体をAで割った式をとする。
となるので、右辺の平方根さえ求められればそこからB'/2を引いたものが解となる。
剰余環における整数Sの平方根を考える。
幸い今回原始根Rが与えられているので、離散対数問題を解きR^x≡Sとなるxを求めよう。
このxはBabyStep-GiantStepで解くことができる。
xが偶数ならR^(x/2)が平方根となり、xが奇数なら平方根は存在しない。
通常BabyStep-GiantStepは前半パートと後半パートでO(√P)ステップずつ処理を行うが、今回は同じRに対しQ回このアルゴリズムを実行することになる。
愚直にこれを行うとTLEしてしまう。
前後半いずれかは引数Sに依存した処理となるが、片方は依存しないため、前後半いずれかの結果は使いまわすことで処理負荷を半減できる。
その際、両ステップO(√P)ずつ処理を行うのではなく、使いまわしにより1回だけ実行すればいい側のパートを多めに実行することで全体の処理を軽くすることができる。
ll mo,P,R; int Q; ll A,B,C; inline int mulmod(int a,int b,int mo) { int d,r; if(a==0 || b==0) return 0; if(a==1 || b==1) return max(a,b); __asm__("mull %4;" "divl %2" : "=d" (r), "=a" (d) : "r" (mo), "a" (a), "d" (b)); return r; } ll modpow(ll a, ll n = mo-2) { ll r=1; a%=mo; while(n) { if(n&1) r=mulmod(r,a,mo); a=mulmod(a,a,mo),n>>=1; } return r; } ll mod_log(ll a,ll b,ll mo) { // a^x=b static ll pre_a=-1; static unordered_map<ll,int> M; static ll a_sq=1; static int sq=0; int i,j; if(pre_a!=a) M.clear(); if(M.empty()) { pre_a=a; ll ap=1,ar=modpow(a),as=1; for(i=0;i*i<mo/8+4;i++) ap=mulmod(ap,ar,mo); sq=i; for(i=0;i<mo;i+=sq) { if(M.count(as)==0) M[as]=i; as=mulmod(as,ap,mo); } } int c=modpow(b); FOR(i,mo) { if(M.count(c)) return (mo+M[c]+i)%mo; c=mulmod(c,a,mo); } } void solve() { int i,j,k,l,r,x,y; string s; cin>>P>>R; mo=P; cin>>Q; while(Q--) { cin>>A>>B>>C; ll r=modpow(A); A = A*r%mo; B = B*r%mo; C = C*r%mo; if(B%2==1) B+=P; B/=2; C=(P-(C-B*B%P+P)%P)%P; if(C==0) { cout<<(P-B)%mo<<endl; } else { ll a=mod_log(R,C,P); if(a%2==1) { cout<<-1<<endl; continue; } ll D=modpow(R,a/2); ll X=(-B+D+2*P)%P; ll Y=(-B-D+2*P)%P; if(X>Y) swap(X,Y); cout<<X<<" "<<Y<<endl; } } }
まとめ
本番平方完成とBabyStepまで至りつつ、ライブラリ化しておらずバグを作りこんで時間内に解けなかった。