kmjp's blog

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

yukicoder : No.187 中華風 (Hard)

想定解じゃない解法(ただし恐らく想定されている)で解いてしまった。
http://yukicoder.me/problems/448

問題

先ほどのEasyと同じ。
ただし満たすべき X_i, Y_iの対がN個ある。

解法

タイトルから想像できる通り、中国人剰余定理を解くだけで良い。
C++だと桁あふれを起こすので、以下Pythonで解いてます。
結構桁数が増えるので、PyPyじゃないとTLEします。

import sys
import math

def ext_gcd(p,q):
	if q==0:
		return (p,1,0)
	g,y,x = ext_gcd(q,p%q);
	y -= p/q*x
	return (g,x,y)

def cmt(a1,mo1,a2,mo2):
	g,x,y=ext_gcd(mo1,mo2)
	a1%=mo1
	a2%=mo2
	if a1%g != a2%g:
		return (-1,0)
	lcm=mo1*(mo2/g)
	v=a1+((a2-a1)%lcm)*x%lcm*(mo1/g)
	return (((v%lcm)+lcm) % lcm,lcm)

N=input()
P=[]
for i in range(N):
	P.append(map(int,raw_input().strip().split(" ")))

T=[P[0][0],P[0][1]]
for i in range(1,N):
	T2=cmt(T[0],T[1],P[i][0],P[i][1])
	if T2[0]<0:
		print -1
		sys.exit()
	T=[T2[0],T2[1]]

if T[0]==0:
	T[0]=T[1]

print T[0]%1000000007

別解でC++で多倍長を用いずに解く方法もある。
まず複数のY[i]が同じ素因数を持つ場合、一番指数が大きなY[i]だけその素因数を残し、他のY[x]からその素因数を取り除く。
例えば P mod 24 = 5, P mod 28 = 17だったら、2の指数は前者の24の方が大きいので、P mod 28 = 17はP mod 7 = 3とX[i],Y[i]を2^2で割り素因数から2を除く。
こうするとY[i]が互いに素になる。

あとはGarnerのアルゴリズムをそのまま適用すると良い。
(自分は残念ながらまだ理屈を理解していないが…)
X[i]がすべて0の時、解はLCM(Y[i])になるので注意。

ll ext_gcd(ll p,ll q,ll& x, ll& y) { // get px+qy=gcd(p,q)
	if(q==0) return x=1,y=0,p;
	ll g=ext_gcd(q,p%q,y,x);
	y-=p/q*x;
	return g;
}

ll inv(ll p,ll q) { // return (1/p)%q ( p,q is co-prime)
	ll xx,yy,g=ext_gcd(p,q,xx,yy);
	if(xx<0) xx+=q, yy-=p;
	return xx;
}


ll CRT_garner(vector<pair<ll,ll> > V, ll mo=1000000007) {
	const int prime_max = 40000;
	static int NP,prime[10000],divp[prime_max+3];
	int i,j,k,l,r,x,y; string s;
	int N=V.size();
	set<ll> P;
	vector<int> num;
	
	if(NP==0) {
		for(int i=2;i<prime_max;i++) if(divp[i]==0) {
			prime[NP++]=i;
			for(int j=i*i;j>i&&j<prime_max;j+=i) divp[j]=i;
		}
	}
	FOR(i,N) {
		ll v=V[i].second;
		FOR(j,NP) if(v%prime[j]==0) {
			P.insert(prime[j]);
			while(v%prime[j]==0) v/=prime[j];
		}
		if(v>1) P.insert(v);
	}
	
	num=vector<int>(N,0);
	
	FORR(r,P) {
		y=0;
		FOR(x,N) {
			num[x]=1;
			j=V[x].second;
			while(j%r==0) j/=r, num[x]*=r;
			if(num[y]<num[x]) y=x;
		}
		FOR(x,N) if(x!=y) {
			if(V[x].first%num[x]!=V[y].first%num[x]) return -1;
			V[x].second /= num[x];
			V[x].first %= V[x].second;
		}
	}
	vector<pair<ll,ll> > V2;
	FOR(x,N) if(V[x].second>1) V2.emplace_back(V[x].second,V[x].first);
	sort(V2.begin(),V2.end());
	V=V2;
	N=V.size();
	
	// garner
	FOR(y,N) FOR(x,y) {
		ll k=V[y].second-V[x].second;
		if(k<0) k+=V[y].first;
		V[y].second = k*inv(V[x].first,V[y].first)%V[y].first;
	}
	ll ret=0;
	for(x=N-1;x>=0;x--) ret = (ret*V[x].first+V[x].second)%mo;
	return ret;
}

void solve() {
	
	int N,i,x,y,nz=0;
	vector<pair<ll,ll> > V;
	
	cin>>N;
	FOR(i,N) {
		cin>>x>>y;
		V.emplace_back(x,y);
		nz+=x>0;
	}
	
	if(nz==0) {
		FOR(y,N) FOR(x,y) V[y].second/=__gcd(V[x].second,V[y].second);
		ll ret=1;
		FOR(x,N) ret=ret*V[x].second%1000000007;
		cout<<ret<<endl;
	}
	else {
		cout << CRT_garner(V) << endl;
	}
}

まとめ

解く時間の8割は中国人剰余定理のライブラリの移植でした。
…多倍長整数で通してしまい、出題者に嘲笑われてるような気分です。