kmjp's blog

競技プログラミング参加記です

yukicoder : No.551 夏休みの思い出(2)

方針はあっていたがバグがとり切れなかった。
https://yukicoder.me/problems/no/551

問題

奇素数Pと原始根Rが与えられる。
このPに関する2次方程式 AX^2 + BX + C \equiv 0 (\mod P)がQ個与えられるのでそれぞれ解を求めよ。

解法

通常の二次方程式を思い出し、平方完成してみよう。
まず全体をAで割った式を X^2 + B'X + C' \equiv 0とする。
 (X + B'/2)^2 \equiv -C' + (B'/2)^2となるので、右辺の平方根さえ求められればそこから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まで至りつつ、ライブラリ化しておらずバグを作りこんで時間内に解けなかった。