AOJ2323 Revenge of Champernowne Constant

問題リンク Revenge of Champernowne Constant

  • 概要

チャンパーノウン定数とは、"0."に続いて、正の整数を昇順に連続で並べたような無理数である。
"0"-"9"の文字で構成された文字列Sがチャンパーノウン定数の中で最初に出現する位置はどこか答えよ。答えは10^16未満である。

S <= 100
  • 解法

Revengeとあるようにこれの類題です。
Sの中のh番目の文字からk文字を抜き出し、これが1つの整数xと解釈することにします。そして、Sの文字列をカバーするように、
..."x-3"+"x-2"+"x-1"+"x"+"x+1"+"x+2"...
という風に文字列を拡大します。Sをカバーしたら、Sに相当する部分と比較し、完全一致するかを調べます。完全一致した場合、チャンパーノウン定数中で、数xが登場するインデックスを算出します。数xはSの先頭からh番目にあるわけなので、この値からhを引いたものがすなわちtargetの開始インデックスとなります。これを(h, k)の組み合わせをざっと調べて、その最小値が答えとなります。Sのh番目からk文字を抜き出したときにリーディング0になっていた場合はアウトです。
このh番目からk文字を抜き出す処理ですが、k <= min(17, |S|)で十分といえます。17桁以上の整数で解釈すると、きっと10^16を超えるし、|S|以上の文字列で解釈しても、それより小さい解が必ず存在するので計算するだけムダです。
h番目からk文字取りだすとき、targetの文字が足りない場合があります。このとき、hより手前の文字列に注目すればOKです。k <= min(17, |S|)としているので、足りない文字数以上の文字がhの手前にあります。例えば、"123412"でh=4, k=4としたとき、hからk文字取ると"12??"となります。手前にある"1234"から後ろ2文字を持ってきて("34")、更に1を加えて("35")、これをくっつけて"1235"と解釈すればうまくいきます。桁上がりが生じるときも気にせず、このまま処理すれば大丈夫です。
例えば、"9994"でh=3, k=4のとき"4???"となり、手前の文字列から足りない3文字を取り("999")、1を加えて("1000")、後ろ3文字を取ってくっつければ"4000"になります。

  • ソース
import java.util.Scanner;

//Revenge of Champernowne Constant
public class AOJ2323 {

	long DIGIT_LIMIT = 1000000000000000000L;
	long MAX_NUMBER = 59477124183006535L;
	long[] endK;
	long[] ten;
	
	void init(){
		endK = new long[20];
		endK[0] = 0;
		long num = 9;
		for(int k=1;k<=17;k++,num*=10){
			endK[k] = endK[k-1]+k*num;
		}
		endK[18] = DIGIT_LIMIT;
		endK[0] = -1;
		ten = new long[20];
		ten[0] = 0;
		ten[1] = 10;
		for(int k=2;k<=17;k++)ten[k]=ten[k-1]*10;
	}
	
	long findNumber(long x){
		if(x<=0||MAX_NUMBER<x)return DIGIT_LIMIT;
		if(x<10)return x;
		int k = (x+"").length()-1;
		return endK[k]+1+(k+1)*(x-ten[k]);
	}
	
	long findString(String target){
		boolean allzero = true;
		for(char ch:target.toCharArray())allzero&=ch=='0';
		if(allzero)return findNumber(Long.parseLong("1"+target))+1;
		int len = target.length();
		if(len==1){
			return findNumber(Long.parseLong(target));
		}
		long res = DIGIT_LIMIT;
		int lenMax = Math.min(17, len);
		for(int h=0;h<lenMax;h++)for(int k=1;k<=lenMax;k++)res = Math.min(res, findStringSub(target, h, k));
		return res;
	}
	
	long findStringSub(String target, int h, int k){
		if(target.charAt(h)=='0')return DIGIT_LIMIT;
		String sub = target.substring(0, h);
		String s = "";
		for(int i=h;i<h+k;i++){
			if(i<target.length())s+=target.charAt(i);
			else {
				int L = h+k-i;
				String r = (Long.parseLong(sub.substring(sub.length()-L))+1)+"";
				while(r.length()-L<0)r="0"+r;
				s+=r.substring(r.length()-L); break;
			}
		}
		long base = Long.parseLong(s);
		if(MAX_NUMBER<base)return DIGIT_LIMIT;
		String match = base+"";
		int pos = h, addLen = 0;
		long b = base-1;
		while(pos>0){
			if(b<=0)return DIGIT_LIMIT;
			String add = b-- +"";
			pos-=add.length();
			addLen+=add.length();
			match = add+match;
		}
		b = base+1;
		pos = h+k;
		while(pos < target.length()){
			String add = b++ +"";
			pos+=add.length();
			match+=add;
		}
		for(int i=0;i<target.length();i++)if(target.charAt(i)!=match.charAt(addLen-h+i))return DIGIT_LIMIT;
		return findNumber(base)-h;
	}
	
	void run(){
		init();
		Scanner sc = new Scanner(System.in);
		for(;;){
			String s = sc.next();
			if("#".equals(s))break;
			System.out.println(findString(s));
		}
	}
	
	public static void main(String[] args) {
		new AOJ2323().run();
	}
}