AOJ2142 Bitwise Kingdom

問題リンク Bitwise Kingdom

  • 概要

[0, 2^N)の整数を表すN文字のビット文字列の中でM番目に小さいものを求めよ。
ただし、文字列の比較順序は次の順序で行われる
1. 文字列中に含まれる1の数が多い方が大きい
2. 1の数が同じならば辞書式で大きい方が大きい
1 <= N <= 60
1 <= M <= 2^M

  • 解法

与えられたNとMを使えば、M番目の文字列に含まれる1の個数Cが分かります。すると、C個の1を含む、長さNのビット文字列の中でM'番目に小さいものを求めるという問題に変わります。
この文字列を再帰を使って求めます。
再帰には、「L番目〜R番目の文字列を作ることができる」という範囲を引数に持たせておきます。
使っていない1の個数がcで、k番目のインデックスの文字を決める場合を考えます。
まず、[L,R]の中にM'が無ければ作ることができません。刈ります。
次に、k番目の文字を0としてみます。このとき、作ることができる文字列の範囲は上限が狭まります。k番目を1とした場合に作ることができる文字列の種類はn-k-1Ccであり、この数だけ上限が狭まることになります。つまり、[L,R]だった作成可能範囲が[L, L+(n-k-1Cc)-1]になります。
k番目を1としてみます。今度は、下限が狭まります。k番目を0としたときに作ることができる文字列の種類はn-k-1Ccであり、この数だけ下限が狭まります。[L,R]だった範囲が[L+(n-k-1Cc), R]になります。

  • ソース
import java.util.Scanner;

//Bitwise Kingdom
public class AOJ2142 {

	int n;
	long M;
	char[] r;
	long[][] comb;

	boolean dfs(int k, long L, long R, int c){
		if(!(L<=M&&M<=R))return false;
		if(k==n){
			return c==0;
		}
		if(n-k-1>=c){
			r[k] = '0';
			if(dfs(k+1, L, L+comb[n-k-1][c]-1, c))return true;
		}
		if(c==0)return false;
		r[k] = '1';
		long up = 0;
		if(n-k-1>=c)up += comb[n-k-1][c];
		return dfs(k+1, L+up, R, c-1);
	}

	long[][] comb(int N){
		long[][] r = new long[N+1][N+1];
		r[0][0]=1;
		for(int i=1;i<=N;i++){
			r[i][0]=r[i][i]=1;
			for(int j=1;j<i;j++){
				r[i][j] = r[i-1][j-1]+r[i-1][j];
			}
		}
		return r;
	}

	void run(){
		Scanner sc = new Scanner(System.in);
		comb = comb(60);
		for(;;){
			n = sc.nextInt(); M = sc.nextLong();
			if((n|M)==0)break;
			long s = 0;
			int c = 0;
			for(;c<=n;c++){
				s += comb[n][c];
				if(M<=s)break;
			}
			s -= comb[n][c];
			M -= s;
			r = new char[n];
			dfs(0, 1, comb[n][c], c);
			System.out.println(new String(r));
		}
	}

	public static void main(String[] args) {
		new AOJ2142().run();
	}
}