こんなの自分の知識では解説なしに解けません…。
http://yukicoder.me/problems/580
問題
N個の異なる奇素数p[i]が与えられる。
p[i]の積であるPを、(p[i]-1)の積であるQを用いて素因数分解したい。
f(X,Q)をXの素因数分解した結果を返す関数とする。
この関数は以下のように定義され、Pの約数を素因数分解するのに利用できる。
- X=1なら空集合を返す。
- Xが素数なら{X}を返す。
- 1~Xの範囲の整数を等確率で選び、その値をrとする。さらにgをGCD(r,X)とする。
- g==Xならf(X,Q)を再帰的に行う。
- 1<g<XならgはXの約数なので、f(g,Q)∪f(X/g,Q)を再帰的に行う。
- g==1の時、Q=2^e*q (qは奇数)と表現できるとして、c=r^p (mod X)を求め、c^2 != 1 (mod X)である限りc = c^2 (mod P)で更新する。
- c=1またはc=X-1ならf(X,Q)を再帰的に行う。
- 1<c<X-1ならhをGCD(c-1,X)とし、f(h,Q)∪f(X/h,Q)を再帰的に行う。
f(P,Q)を求める際、関数fを呼ぶ回数の期待値を求めよ。
解法
Writer解説そのままなので、概要はそちらを見た方が良いと思います。
Xがp[i]のうちどれの積であるか、を状態としてbitDPまたはメモ化再帰で処理する。
上記手順の3-2までは割と簡単。
というのもrに対し、1<g<Xとなる確率は簡単に求められる。
gはp[i]のうち一部a[i]の積であり、残りX/rはp[i]のうち一部b[i]の積であるとする。
rがそのようなgの倍数となる確率は、rがa[i]のすべてで割りきれてb[i]のいずれでも割れない確率なので1/a[i]の積に(1-(1/b[i]))の積を掛ければよい。
問題はg==1の時にcやhがどうなるかである。
フェルマーの小定理よりr^Q = 1 (mod P)なので、(r^q)を2の累乗乗するといずれ1になる。
その際、最大でもe回2乗すれば1になる。
各rに対し、何回2乗すればよいかを考えると、2^eの巡回群より以下の確率になることがわかる。
- i=0 : 1/(2^e)
- i=1~e : 1/(2^(e+1+i))
hが同様にa[i]の積の倍数であってb[i]の倍数でない確率は以下のように求められる。
a[i]-1=2^e[i]*q[i]、b[i]-1=2^g[i]*s[i]と表現できるとする。(q,sは奇数)
各kにおいて以下の積:
- 各a[i]においてk回累乗すると1になる確率の積
- 各b[i]においてk回未満累乗した時点で1になる確率の積
g==1の時の確率に、上記積のk毎の和を掛けると、3-3の手順でhがa[i]とb[i]に分割できる確率が求められる。
int T; int N; ll P[20]; int bi[20]; int minbi[1<<15]; double bip[20][65]; double bipsum[20][65]; double memo[1<<15]; double dodo(int pm) { if(__builtin_popcount(pm)<=1) return __builtin_popcount(pm); if(memo[pm]>=0) return memo[pm]; vector<int> id; double ret=0; double left=1, p2b=1; int mask,i,x; FOR(i,N) if(pm&(1<<i)) id.push_back(i), p2b *= (P[i]-1.0)/P[i]; int n=id.size(); FOR(mask,1<<n) { double p1=1; int nm[2]={}; FOR(i,n) { if(mask&(1<<i)) p1 *= 1.0/P[id[i]], nm[0] |= 1<<id[i]; else p1 *= (P[id[i]]-1.0)/P[id[i]], nm[1] |= 1<<id[i]; } if(nm[0] && nm[1]) { int mb=minbi[nm[0]]; double p2=0; for(x=1;x<=63;x++) { double hoge=1; FOR(i,n) { if(mask&(1<<i)) hoge *= bip[id[i]][x]; else hoge *= bipsum[id[i]][x-1]; } p2+=hoge; } double pat = p1 + p2*p2b; left -= pat; ret += pat * (dodo(nm[0])+dodo(nm[1])); } } return memo[pm]=(1+ret)/(1-left); } void solve() { int i,j,k,l,r,x,y; string s; cin>>N; FOR(i,1<<N) memo[i]=-1; ZERO(bip); ZERO(bipsum); FOR(i,N) { cin>>P[i]; ll p=P[i]-1; bi[i]=0; while(p%2==0) bi[i]++, p/=2; bipsum[i][0]=bip[i][0]=pow(0.5,bi[i]); for(x=1;x<=64;x++) { if(x<=bi[i]) bip[i][x]=pow(0.5,bi[i]+1-x); bipsum[i][x]=bipsum[i][x-1]+bip[i][x]; } } FOR(i,1<<N) { minbi[i]=100; FOR(j,N) if(i & (1<<j)) minbi[i]=min(minbi[i],bi[j]); } _P("%.12lf\n",dodo((1<<N)-1)); }
まとめ
Euler勢怖い。