我佛了,這CF竟然沒有官方題解。ide
題意:給定k,t,求第k小的16進制數,知足每一個數碼的出現次數不超過t。函數
解:spa
每一個數都有個出現次數限制,搞不倒。一開始想到了排序hash數位DP,不過寫了寫以爲不勝其煩,就棄療了。設計
可是思考一下,若是咱們知道了每一個數的出現次數和數的位數,那麼一次普通DP就可以求出方案數。rest
因此咱們暴力作屢次這種普通DP便可......code
具體來講,分爲帶前導0和不帶前導0兩個DP函數。blog
首先枚舉數的長度,計算不帶前導0的個數。若是不到k就減去。排序
而後知道了長度,再一位一位的肯定。在每一位上枚舉放哪一個數碼。若是方案數不足就減去這麼多。string
對於那個DP函數,狀態設計f[i][j]表示用前i個數碼放j位的數的方案數。轉移就是hash
f[i][j] = f[i - 1][j - k] * C(j, k),表示在j個位置中選出k個放數碼i,剩下的放前面的數碼,前面的數碼相對位置不變。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 5 typedef long long LL; 6 const int N = 200, B = 16; 7 8 LL f[17][N], C[N][N], choose[N]; 9 int rest[B]; 10 11 inline LL DP(int n) { // with leading zero 12 if(n <= 0) { 13 return 1; 14 } 15 memset(f, 0, sizeof(f)); 16 for(int i = 1; i <= B; i++) { 17 f[i - 1][0] = 1; 18 for(int j = 1; j <= n; j++) { 19 for(int k = 0; k <= rest[i - 1] && k <= j; k++) { 20 f[i][j] += f[i - 1][j - k] * C[j][k]; 21 } 22 } 23 } 24 25 return f[16][n]; 26 } 27 28 inline LL DP1(int n) { // no leading zero 29 LL ans = 0; 30 for(int i = 1; i < B; i++) { 31 if(rest[i]) { 32 rest[i]--; 33 ans += DP(n - 1); 34 rest[i]++; 35 } 36 } 37 return ans; 38 } 39 40 int main() { 41 for(int i = 1; i < 200; i++) { 42 C[i][0] = C[i][i] = 1; 43 for(int j = 1; j < i; j++) { 44 C[i][j] = C[i - 1][j] + C[i - 1][j - 1]; 45 } 46 } 47 int t; 48 LL k; 49 scanf("%lld%d", &k, &t); 50 int len; 51 for(len = 1; ; len++) { 52 for(int i = 0; i < B; i++) { 53 rest[i] = t; 54 } 55 LL temp = DP1(len); 56 if(temp >= k) { 57 break; 58 } 59 k -= temp; 60 } 61 62 for(int i = 0; i < B; i++) { 63 rest[i] = t; 64 } 65 for(int i = len; i >= 1; i--) { 66 for(int j = (i == len); j < B; j++) { 67 if(!rest[j]) { 68 continue; 69 } 70 rest[j]--; 71 LL temp = DP(i - 1); 72 if(temp < k) { 73 k -= temp; 74 rest[j]++; 75 } 76 else { 77 choose[i] = j; 78 break; 79 } 80 } 81 } 82 83 for(int i = len; i >= 1; i--) { 84 if(choose[i] < 10) { 85 printf("%d", choose[i]); 86 } 87 else { 88 putchar('a' + choose[i] - 10); 89 } 90 } 91 return 0; 92 }