kmjp's blog

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

yukicoder : No.14 最小公倍数ソート

難しいと思ったら☆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]);
}

まとめ

なかなか面白い問題でした。