難しいと思ったら☆2→☆3になってた。
http://yukicoder.me/problems/38
問題
N要素の数列A[i]がある。
最小公倍数ソートとは、以下のソート法である
- まずA[0]を固定する。A[1]~A[N-1]をA[0]との最小公倍数順でソートする。最小公倍数が等しいとき、元の数が小さい方が前に来る。
- 次にA[1]を固定する。A[2]~A[N-1]をA[1]との最小公倍数順でソートする。最小公倍数が等しいとき、元の数が小さい方が前に来る。
- 以後A[i]を固定してA[i+1]~A[N-1]をA[i]との最小公倍数順でソートする、という処理を繰り返す。
ソート後のAを求めよ。
解法
O(N^2)の解法として、A[i]に対しA[i+1]からA[N-1]のうちA[i]との最小公倍数が最小のものを持ってきてA[i+1]に置く、という処理を繰り返せばよい。
N<=10000なのでギリギリ4秒台で間に合う。
別の解法もある。
A[i]が小さいことを利用する。
A[i]の約数を列挙し、各約数に対応したsetに値を追加する。
A[i]に対し、A[i+1]~A[N-1]を全部LCMを出すのではなく、A[i]の約数ごとにsetから同じ約数を持つ最小値を取り出し、LCMを探索する。
これだとA[i]の約数個分だけsetを検索するだけなのでだいぶ高速化される。
低速版:
int N; int A[100010]; void solve() { int i,j,k,l,r,x,y; string s; cin>>N; FOR(i,N) cin>>A[i]; FOR(i,N-1) { _P("%d ",A[i]); x=i+1; y=A[i]*A[x]/__gcd(A[i],A[x]); for(j=i+2;j<N;j++) { r=A[i]*A[j]/__gcd(A[i],A[j]); if(r<y || (r==y&&A[j]<A[x])) x=j, y=r; } swap(A[x],A[i+1]); } _P("%d\n",A[N-1]); }
高速版:
int N; int A[100010]; vector<int> divs[10001]; set<pair<int,int> > S[10001]; void solve() { int i,j,k,l,r,x,y; string s; cin>>N; FOR(i,N) { cin>>A[i]; for(j=1;j*j<=A[i];j++) if(A[i]%j==0) { S[j].insert(make_pair(A[i],i)); S[A[i]/j].insert(make_pair(A[i],i)); divs[i].push_back(j); if(j*j!=A[i]) divs[i].push_back(A[i]/j); } } int id=0; FOR(i,N-1) { _P("%d ",A[id]); r=1<<30; ITR(it,divs[id]) { S[*it].erase(make_pair(A[id],id)); if(S[*it].empty()) continue; pair<int,int> p=*S[*it].begin(); y=p.first*A[id]/__gcd(p.first,A[id]); if(y<r || (y==r && p.first<A[x])) x=p.second, r=y; } id = x; } _P("%d\n",A[id]); }
まとめ
なかなか面白い問題でした。