1. 程式人生 > >BZOJ 2425 [HAOI2010]計數:數位dp + 組合數

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

scanf ++ www. efi 題意 lin namespace pac strlen

題目鏈接: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 }

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