kmjp's blog

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

World CodeSprint 8 : E. Decibinary Numbers

これもちょっと手間取ったが、定番テクで行ける。
https://www.hackerrank.com/contests/world-codesprint-8/challenges/decibinary-numbers

問題

通常2進数は各桁0か1の値をとり、10進数は0~9の値をとる。
10進数の整数を、無理やり2進数とみなすことを考える。
たとえば2016をこの方法で10進数で表現しなおすと2*8+0*4+1*2+6=24となる。

元々10進数の値Dについて、上記手法で表現しなおした値をf(D)とする。
f(D)を小さい順に並べたとき、x番目に来るような元のDを求めよ。
なお、f(D)=f(D')のときは、元の10進数表記で小さいほうの値が前に来る。

解法

xの上限は10^16である。この時、Dの最大値を求めてみよう。

以下の状態を考え、桁DPの要領でSをdの小さい順に求めていってみよう。
S(d, v) := d桁以下の整数pのうち、f(p)=vとなるものの数

f(10^19)=2^19となるが、f(p)=vとなるpの組み合わせを求めてみると、vが2^19未満となるpが10^16個以上ある。
よって解は10^19未満であることは明らかである。
S(19,v)を2分探索すると、x番目の値pに対するf(p)が求められる。
あとは辞書順x個目を求める定番テクとして、f(p)が等しくなる整数のうち解を上位の桁から決めていけばよい。

ll dp[21][300005];
ll sum[300005];
int Q;
ll X;
ll p10[22];

ll dfs(int d,ll K,int L) {
	ll ret=0;
	int i;
	if(d==0) return L;
	FOR(i,10) {
		if(K<=dp[d][L-(i<<d)]) {
			ret = p10[d]*i + dfs(d-1,K,L-(i<<d));
			break;
		}
		else {
			K -= dp[d][L-(i<<d)];
		}
	}
	return ret;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	dp[0][0]=1;
	int d;
	for(d=0;d<20;d++) FOR(x,300000) if(dp[d][x]) FOR(y,10) if(x+(y<<d)<300000) dp[d+1][x+(y<<d)] += dp[d][x];
	p10[0]=1;
	FOR(i,21) p10[i+1]=p10[i]*10;
	
	FOR(i,300000) sum[i]=(i?sum[i-1]:0)+dp[d][i];
	
	cin>>Q;
	while(Q--) {
		cin>>X;
		if(X==1) {
			cout<<0<<endl;
			continue;
		}
		x=lower_bound(sum,sum+300000,X)-sum;
		X-=sum[x-1];
		cout<<dfs(20,X,x)<<endl;
	}
	
}

まとめ

なんかHackerRankは微妙にテストケース数が多かったりなんか独特な印象。