kmjp's blog

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

AtCoder ARC #042 : D - あまり

以前このアルゴリズム見たときは「すげー」と思ったけど、この正解者数だと割と有名なアルゴリズムなのかな。
…と思ったら埋め込みや愚直ループで通してる人も結構いたようだ。
http://arc042.contest.atcoder.jp/tasks/arc042_d

問題

以下の条件を満たす整数X,P,A,Bが与えられる。

  • これらは乱数生成機で生成されている。
  • Pは素数である。
  • 1≦X<P<2^31、0≦A≦B<2^31

X^i mod P (A≦i≦B)の最小値を求めよ。

解法

以下の2つの考え方を組み合わせる。
yukicoder : No.97 最大の値を求めるくえり - kmjp's blog
UTPC 2014: L - セミ時雨ハッシュ - kmjp's blog

まず簡単なケースを処理してしまおう。
フェルマーの小定理より、A~Bの間に(P-1)の倍数があれば、解は1である。
以下、A~Bの間に(P-1)の倍数がない場合を考える(A,Bは(P-1)の剰余を取った値にしておく)

B-Aが比較的小さい(10^8位?)までなら、愚直にX^A~X^Bを総当たりしてしまおう。
B-Aが大きい場合、10^8以上なら、X^A~X^Bは1~(P-1)の間で(B-A+1)個の値を取ることになるので、Pが2^31近い場合でも1/20位の割合で含まれることになる。

よって、X^i=1,2,3.... となるiを順に求めていけば、20位まで探索する間にA≦i≦Bとなるiが現れることが期待できる。

あとはX^i=Qとなるiの求め方だが、これはいわゆる離散対数問題であり、上記UTPCの問題のようにBaby-Step Giant-Stepアルゴリズムで解くことができる。
自分が(UTPCを解いた当時)参考にしたのは以下のPDF。
https://math.berkeley.edu/~sagrawal/su14_math55/notes_shank.pdf

ll X,A,B,P,XA;
ll mo;
map<ll,int> MM;

ll modpow(ll a, ll n = mo-2) {
	ll r=1;
	while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1;
	return r;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>X>>P>>A>>B;
	if(A/(P-1) != B/(P-1)) return _P("1\n");
	A %= (P-1);
	B %= (P-1);
	
	mo=P;
	
	XA=modpow(X,A);
	B-=A;
	
	if(B<=100000000) {
		ll mi=XA;
		FOR(i,B) {
			XA=XA*X%mo;
			mi=min(mi,XA);
		}
		cout<<mi<<endl;
	}
	else {
		
		ll cur=1, sq;
		for(sq=0;sq*sq<=P;sq++) {
			MM[cur]=sq;
			cur=cur*X%P;
		}
		
		ll re=modpow(X),xsq=1;
		FOR(i,sq) xsq=xsq*re%P;
		
		ll XAr=modpow(XA);
		for(ll a=1;a<=P;a++) {
			ll tar=a*XAr%mo;
			ll st=1;
			FOR(i,sq) {
				if(MM.count(tar) && sq*i+MM[tar]<=B) return _P("%lld\n",a);
				tar=tar*xsq%mo;
			}
		}
	}
}

まとめ

UTPCちゃんと解いておいてよかった。