题目链接: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 }