BZOJ 2425 [HAOI2010]计数:数位dp + 组合数

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2425

题意:

  给你一个数字n,长度不超过50。

  你可以将这个数字:

    (1)去掉若干个0

    (2)打乱后重新排列

  问你可以产生多少个小于n的数字。

 

题解:

  题目中的第一个操作其实是没有用的。

  去掉若干个0之后再重新排列(不允许前导0),和不去0直接重新排列(允许前导0),其实是等价的。

 

  所以按照数位dp的方法从高到低按位统计。

  如n = 2345时,分别统计前缀为0~1, 20~22, 230~233, 2340~2344的答案。

 

  最高位为第1位。

  假设当前考虑到第i位,1~i-1位都和原数字n完全匹配。

  枚举第i位可以填了x∈[0,a[i]),则先让cnt[x]--。

  然后就是i+1位之后的数如何填了。

  设len = n-i。

  方案数 = 先从len个位置中找了cnt[0]个位置全填0的方案数 * 又从(len-cnt[0])个位置中找了cnt[1]个位置全填1的方案数...

  方案数 = C(len,cnt[0]) * C(len-cnt[0],cnt[1]) * C(len-cnt[0]-cnt[1],cnt[2])...

  最后再让cnt[x]++回来,然后cnt[a[i]]--就好了。

 

AC Code:

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string.h>
 4 #define MAX_N 55
 5 #define MAX_D 15
 6 
 7 using namespace std;
 8 
 9 int n;
10 long long ans=0;
11 long long a[MAX_N];
12 long long cnt[MAX_N];
13 long long c[MAX_N][MAX_N];
14 char s[MAX_N];
15 
16 void read()
17 {
18     scanf("%s",s+1);
19     n=strlen(s+1);
20     for(int i=1;i<=n;i++) cnt[a[i]=s[i]-'0']++;
21 }
22 
23 void cal_c()
24 {
25     c[0][0]=1;
26     for(int i=1;i<=n;i++)
27     {
28         c[i][0]=1;
29         for(int j=1;j<=i;j++)
30         {
31             c[i][j]=c[i-1][j]+c[i-1][j-1];
32         }
33     }
34 }
35 
36 long long cal_p(int len)
37 {
38     long long now=1;
39     for(int i=0;i<=9;i++)
40     {
41         now*=c[len][cnt[i]];
42         len-=cnt[i];
43     }
44     return now;
45 }
46 
47 void cal_ans()
48 {
49     for(int i=1;i<=n;i++)
50     {
51         for(int j=0;j<a[i];j++)
52         {
53             cnt[j]--;
54             ans+=cal_p(n-i);
55             cnt[j]++;
56         }
57         cnt[a[i]]--;
58     }
59 }
60 
61 void work()
62 {
63     cal_c();
64     cal_ans();
65     printf("%lld\n",ans);
66 }
67 
68 int main()
69 {
70     read();
71     work();
72 }

 

posted @ 2018-03-12 08:43  Leohh  阅读(260)  评论(0编辑  收藏  举报