BZOJ 2425 [HAOI2010]計數:數位dp + 組合數

Leohh發表於2018-03-12

題目連結: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 }

 

相關文章