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 }
AC代码

 

转载于:https://www.cnblogs.com/huyufeifei/p/10206158.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值