TopCoder SRM 546 Div1 Medium FavouriteDigits
問題
digit1という数字をcount1個以上含み、
digit2という数字をcount2個以上含む、N以上の最小の数を求めよ。
制約条件
0≦digit1, digit2≦9
count1+count2≦15
Nは10^15以下。
答えは必ずlong longに収まる。
方針
n以下でd1をc1個以上含み、d2をc2個以上含むような数をf(n)と置く。
f(n)は桁DPにより簡単に求まる。
f(k) = f(N-1) + 1となる最小のkを見つけるのが問題なので、
これは二分探索をすればいい。
桁DPは次のような感じ。
dp[先頭からの桁数][digit1の個数][digit2の個数][nより小さいか][leadingzeroが終わったか]
これに、次の桁を10通り試して更新していくようなDP.
ソースコード
int m, d[20]; ll dp[20][20][20][2][2]; ll calc(ll n, int d1, int c1, int d2, int c2){ for(m = 0; n; n /= 10) d[m++] = n % 10; reverse(d, d + m); memset(dp, 0, sizeof(dp)); dp[0][0][0][0][0] = 1; rep(i, m) rep(j, c1 + 1) rep(k, c2 + 1) rep(l, 2) rep(g, 2){ if(!dp[i][j][k][l][g]) continue; rep(h, 10){ int ng = g || h; int nl = l || h < d[i]; int nj = min(c1, j + (ng && h == d1)); int nk = min(c2, k + (ng && h == d2)); if(!nl && h > d[i]) continue; dp[i + 1][nj][nk][nl][ng] += dp[i][j][k][l][g]; } } return dp[m][c1][c2][0][1] + dp[m][c1][c2][1][1]; } class FavouriteDigits { public: long long findNext(long long N, int digit1, int count1, int digit2, int count2) { ll lo = N - 1, hi = (ll)2e18, mid; ll res = calc(N - 1, digit1, count1, digit2, count2) + 1; while(lo + 1 < hi){ mid = (lo + hi) / 2; if(calc(mid, digit1, count1, digit2, count2) >= res) hi = mid; else lo = mid; } return hi; } };