SRM 437 Div1 Medium TheInteger

問題概要

整数nが与えられる。
n以上で、数字がちょうどk種類であるような、最小の整数を求めよ。

制約条件

n≦10^18
1≦k≦10

方針

場合分けで解こうとしたら解けなかった。


dp[i][mask][j]を、
数字をi番目まで見て、maskで表せる数字を使い、
j=1ならi番目まででnより大きくなっている状態の、最小の数とする。


すると、下のように表を更新するdpができる。

ソースコード

typedef unsigned long long ll;
ll dp[20][1<<10][2];

class TheInteger {
	public:
	long long find(long long n, int k) 
	{
		int d[20],m=0;
		for(ll i=n;i;i/=10)d[m++]=i%10;
		reverse(d,d+m);
		
		if(m<k){
		//nの桁数がkより小さかったら102345のような答えを直接作る。
			int a[10],ans=0; rep(i,k)a[i]=i;
			swap(a[0],a[1]);
			rep(i,k)ans*=10,ans+=a[i];
			return ans;
		}
		
		rep(i,20)rep(j,1<<10)rep(k,2)dp[i][j][k]=~0ll;
		rep(i,10)if(i>=d[0])dp[0][1<<i][i>d[0]]=i;
		
		rep(i,m-1)rep(mask,1<<10)rep(j,2)if(dp[i][mask][j]!=~0ll)rep(k,10){
			
			if(k<d[i+1]&&j==0)continue;
			ll &next=dp[i+1][mask|1<<k][j||k>d[i+1]];
			next=min(next,dp[i][mask][j]*10+k);
		}
		
		ll ans=~0ll;
		rep(i,1<<10)rep(l,2){
			int b=0;
			for(int j=i;j;j&=j-1)b++;
			if(b!=k)continue;
			ans=min(ans,dp[m-1][i][l]);
		}
		if(ans==~0){
		//答えがnと同じ桁数に存在しなかった場合、(nの桁)+1桁の最小の数が答え。
		//1000...000234みたいなものを作る。
			int a; ans=0;
			rep(i,m+1){
				if(i==0)a=1;
				else if(1<=i&&i<m-k+3)a=0;
				else a=i+k-m-1;
				
				ans*=10; ans+=a;
			}
			return (long long)ans;
		}
		return (long long)ans;
	}
};