読者です 読者をやめる 読者になる 読者になる

kanetaiの二次記憶装置

プログラミングに関するやってみた、調べた系のものをQitaに移して、それ以外をはてブでやる運用にしようと思います。http://qiita.com/kanetai

Saruman's Army(PKU No.3069)

Saruman's Army(PKU No.3069)

N個の点が直線状にある。点iの位置はX_i。N個のうちいくつかの点を選び、それらの点に印をつける。N個の全ての点について、距離がR以内の場所に印をつけられた点がなければならない。条件を満たす、印をつけた点の最小の個数を求める。
制約
1 \leq N \leq 1000
0 \leq R \leq 1000
0 \leq X_i \leq 1000

アルゴリズム

カバーされていない最も左の点からからR以内にある最も右の点に印をつける貪欲法。最も左の点から単に2R以内のものをカバーできるとは限らないことに注意(最も左の点+Rの地点に点があるとは限らない)。
C++ならstd::upper_boundを使って簡単に記述できる。

コード

import java.util.*;
public class pku3069 {
	static Scanner scanner;
	public static void main(String[] args) {
		scanner = new Scanner(System.in);
		new pku3069();
	}
	pku3069(){
		while(true){
			int R = scanner.nextInt();
			int N = scanner.nextInt();
			if( R==-1 && N==-1 ) break;
			int[] x = new int[N+1];
			x[N] = Integer.MAX_VALUE; //solve2でx[N]を番兵として使用する
			for(int i=0; i<N; i++) x[i] = scanner.nextInt();
			
			System.out.println( solve(N,R,x) );
		}
	}
	
	int upper_bound(int[] array, int li, int ri, int k){
		int lb = li - 1, ub = ri;
		//解の存在範囲が1より大きい間、反復
		while(ub - lb > 1){
			int mid =(lb + ub) / 2;
			if( array[mid] <= k )	//midが条件を満たせば、解の存在範囲は[mid,ub)
				lb = mid;
			else				//midが条件を満たさなければ、解の存在範囲は[lb,mid)
				ub = mid;
		}
		return ub;
	}
	//greedy 2分探索(upper_bound) ソートしているのでO(n log n)
	int solve(int N, int R, int[] x){
		Arrays.sort(x);
		int res = 0;
		for(int i = 0; i < N; ++res){
			i = upper_bound(x, 0, N, x[i]+R);
			i = upper_bound(x, i-1, N, x[i-1]+R);
		}
		return res;
	}
	//greedy 線形探索  ソートするのでO(n log n)
	int solve2(int N, int R, int[] x){
		int ans = 0;
		int i = 0;
		Arrays.sort(x);
		while( i < N ){
			int s = x[i++]; //左端
			while( x[i] <= s+R ) i++;
			int p = x[i-1]; //中央
			while( x[i] <= p+R ) i++;
			//x[i-1]が右端
			ans++;
		}
		return ans;
	}
}