CF747F Igor and Interesting Numbers
我佛了,这CF居然没有官方题解。
题意:给定k,t,求第k小的16进制数,满足每个数码的出现次数不超过t。
解:
每个数都有个出现次数限制,搞不倒。一开始想到了排序hash数位DP,不过写了写觉得不胜其烦,就弃疗了。
但是思考一下,如果我们知道了每个数的出现次数和数的位数,那么一次普通DP就能够求出方案数。
所以我们暴力做多次这种普通DP即可......
具体来说,分为带前导0和不带前导0两个DP函数。
首先枚举数的长度,计算不带前导0的个数。如果不到k就减去。
然后知道了长度,再一位一位的确定。在每一位上枚举放哪个数码。如果方案数不足就减去这么多。
对于那个DP函数,状态设计f[i][j]表示用前i个数码放j位的数的方案数。转移就是
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 }