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